A
Andrus
Marc,
Using your code and hints I created test case below.
Issues:
1. I need that business objects derive from my base class.
How to change this code so that EntityBase<T> class is derived from object,
not form Bag ?
2. How to add dynamic calculated property which returns "Mr "+name as value?
Property value calculation can be built-in. I'm interested is it possible
to add calculated property at all.
3. Will Castle Activerecord/NHiberante lazy mode support this kind of
business objects ?
Lazy mode uses Castle DynamicProxy to catch all propeperties.
4. How to simpify this code? It looks complicated and messy.
5. In RDL report expressions I need to get dynamic property value from
static method like
class DynamicCustomer {
public static string GetName( string id ) {
return ExecScalar("SELECT name FROM customer WHERE id="+id);
}
}
When compiling I create all such methods and Equals() override dynamically.
How to create this and general Equals() methods when properties are added
using TypeDescriptor ?
To use TypeDescriptor I must create replace all GetXXXXXX (where XXXXXX is
property name ) methods
with method with two parameters like
public static string GetStringProperty( string id, string property )
but this is inconvenient to type in report designer ( I need to use hundreds
of such calls in reports).
6. What are [*1] and [*2] in your message?
7. This causes TypeInitializationException in MONO. How to run it in MONO?
How to chage this code so that correct type is identified ?
Andrus.
using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
static class Program {
[STAThread]
static void Main() {
Customer Customer = new Customer();
Customer.AddProperty<string>("Name");
Customer.SetValue("Name", "Marc");
MessageBox.Show(Customer.GetStringValue("Name"));
}
}
// Runtime extensible business object
public class Customer : EntityBase<Customer> {
string id;
public string Id {
get {
return id;
}
set {
id = value;
}
}
}
// from core library
public abstract class EntityBase<T> : Bag {
public virtual void SetValue(string propertyName, string propertyValue) {
PropertyDescriptor p =
TypeDescriptor.GetProperties(typeof(T))[propertyName];
ITypeDescriptorContext ctx = new SimpleContext(this, p);
object val;
val = p.Converter.ConvertFromString(ctx, propertyValue);
p.SetValue(this, val);
}
public virtual string GetStringValue(string propertyName) {
PropertyDescriptor p =
TypeDescriptor.GetProperties(typeof(T))[propertyName];
return p.GetValue(this).ToString();
}
}
interface IBag {
void OnAfterValueChanged(string propertyName);
void AddHandler(object key, EventHandler value);
void RemoveHandler(object key, EventHandler value);
void OnEvent(object key);
IBagValue GetValue(string propertyName);
}
interface IBagValue {
void ResetValue();
bool IsDefaultValue { get;}
object Value { get; set;}
event EventHandler ValueChanged;
}
interface IBagDefinition {
IBagValue Create(IBag bag);
PropertyDescriptor Property { get;}
}
sealed class BagDefinition<T> : IBagDefinition {
private readonly PropertyDescriptor property;
public PropertyDescriptor Property { get { return property; } }
private readonly T defaultValue;
public T DefaultValue { get { return defaultValue; } }
public string Name { get { return Property.Name; } }
IBagValue IBagDefinition.Create(IBag bag) {
return new BagValue<T>(bag, this);
}
public BagDefinition(string propertyName, Attribute[] attributes) {
defaultValue = default(T);
if (attributes != null) { // check for a default value
foreach (Attribute attrib in attributes) {
DefaultValueAttribute defAttrib = attrib as
DefaultValueAttribute;
if (defAttrib != null) {
defaultValue = (T)defAttrib.Value;
break;
}
}
}
property = new BagPropertyDescriptor(propertyName, attributes);
}
internal class BagPropertyDescriptor : PropertyDescriptor {
public BagPropertyDescriptor(string name, Attribute[] attributes)
: base(name, attributes) { }
private IBagValue GetBagValue(object component) {
return ((IBag)component).GetValue(Name);
}
public override object GetValue(object component) {
return GetBagValue(component).Value;
}
public override void SetValue(object component, object value) {
GetBagValue(component).Value = value;
}
public override Type ComponentType {
get { return typeof(Bag); }
}
public override Type PropertyType {
get { return typeof(T); }
}
public override bool IsReadOnly {
get { return false; }
}
public override bool CanResetValue(object component) {
return true;
}
public override void ResetValue(object component) {
GetBagValue(component).ResetValue();
}
public override bool ShouldSerializeValue(object component) {
return !GetBagValue(component).IsDefaultValue;
}
public override bool SupportsChangeEvents {
get { return true; }
}
public override void AddValueChanged(object component,
EventHandler handler) {
GetBagValue(component).ValueChanged += handler;
}
public override void RemoveValueChanged(object component,
EventHandler handler) {
GetBagValue(component).ValueChanged -= handler;
}
}
}
sealed class BagValue<T> : IBagValue, ITypeDescriptorContext {
private T value;
private readonly IBag bag;
void IBagValue.ResetValue() {
Value = Definition.DefaultValue;
}
bool IBagValue.IsDefaultValue {
get {
return EqualityComparer<T>.Default.Equals(Value,
Definition.DefaultValue);
}
}
private readonly BagDefinition<T> definition;
public IBag Bag { get { return bag; } }
public BagDefinition<T> Definition { get { return definition; } }
public BagValue(IBag bag, BagDefinition<T> definition) {
if (bag == null) throw new ArgumentNullException("bag");
if (definition == null) throw new ArgumentNullException("definition");
this.bag = bag;
this.definition = definition;
Value = Definition.DefaultValue;
}
public T Value {
get { return value; }
set {
if (EqualityComparer<T>.Default.Equals(Value, value))
return;
this.value = value;
Bag.OnAfterValueChanged(Definition.Name);
Bag.OnEvent(Definition);
}
}
public event EventHandler ValueChanged {
add { Bag.AddHandler(Definition, value); }
remove { Bag.RemoveHandler(Definition, value); }
}
object IBagValue.Value {
get { return Value; }
set { Value = (T)value; }
}
IContainer ITypeDescriptorContext.Container {
get {
return null;
}
}
object ITypeDescriptorContext.Instance { get { return Bag; } }
void ITypeDescriptorContext.OnComponentChanged() {
Bag.OnAfterValueChanged(Definition.Name);
}
bool ITypeDescriptorContext.OnComponentChanging() { return true; }
PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor {
get { return Definition.Property; }
}
object IServiceProvider.GetService(Type serviceType) {
return null;
}
}
public /*sealed*/ class Bag : IBag, INotifyPropertyChanged {
private EventHandlerList events;
void IBag.AddHandler(object key, EventHandler handler) {
AddHandler(key, handler);
}
void IBag.RemoveHandler(object key, EventHandler handler) {
RemoveHandler(key, handler);
}
void IBag.OnEvent(object key) {
OnEvent(key);
}
private void AddHandler(object key, Delegate handler) {
if (handler == null) return;
if (events == null) events = new EventHandlerList();
events.AddHandler(key, handler);
}
private void RemoveHandler(object key, Delegate handler) {
if (events == null || handler == null) return;
events.RemoveHandler(key, handler);
}
private void OnEvent(object key) {
if (events == null) return;
EventHandler handler = events[key] as EventHandler;
if (handler != null) handler(this, EventArgs.Empty);
}
public event PropertyChangedEventHandler PropertyChanged {
add { AddHandler(EVENT_PropertyChanged, value); }
remove { RemoveHandler(EVENT_PropertyChanged, value); }
}
private static readonly object EVENT_PropertyChanged = new object();
private void OnPropertyChanged(string propertyName) {
if (events == null) return;
PropertyChangedEventHandler handler = events[EVENT_PropertyChanged] as
PropertyChangedEventHandler;
if (handler != null) handler(this, new
PropertyChangedEventArgs(propertyName));
}
void IBag.OnAfterValueChanged(string propertyName) {
OnPropertyChanged(propertyName);
}
IBagValue IBag.GetValue(string propertyName) {
return GetValue(propertyName);
}
private readonly Dictionary<string, IBagValue> values =
new Dictionary<string, IBagValue>(StringComparer.InvariantCulture);
private IBagValue GetValue(string propertyName) {
lock (values) {
IBagValue value;
if (!values.TryGetValue(propertyName, out value)) {
value = CreateValue(this, propertyName);
values.Add(propertyName, value);
}
return value;
}
}
static readonly Dictionary<string, IBagDefinition> defintions =
new Dictionary<string,
IBagDefinition>(StringComparer.InvariantCulture);
public static PropertyDescriptor AddProperty<T>(string propertyName,
params Attribute[] attributes) {
BagDefinition<T> def = new BagDefinition<T>(propertyName, attributes);
lock (defintions) {
defintions.Add(propertyName, def);
BagDescriptionProvider.ResetProperties();
}
return def.Property;
}
internal static PropertyDescriptorCollection GetProperties() {
lock (defintions) {
PropertyDescriptor[] props = new PropertyDescriptor[defintions.Count];
int i = 0;
foreach (IBagDefinition def in defintions.Values) {
props[i++] = def.Property;
}
return new PhantomPropertyDescriptorCollection(props, false);
}
}
internal sealed class PhantomPropertyDescriptorCollection :
PropertyDescriptorCollection {
public PhantomPropertyDescriptorCollection(PropertyDescriptor[]
properties, bool readOnly)
: base(properties, readOnly) {
}
public override PropertyDescriptor Find(string name, bool ignoreCase) {
PropertyDescriptor prop = base.Find(name, ignoreCase);
if (prop == null) {
prop = AddProperty<object>(name);
Add(prop);
}
return prop;
}
}
static IBagValue CreateValue(IBag bag, string propertyName) {
lock (defintions) {
return defintions[propertyName].Create(bag);
}
}
public Bag() { }
static Bag() {
BagDescriptionProvider.Initialize();
}
}
sealed class BagDescriptionProvider : TypeDescriptionProvider {
static readonly BagTypeDescriptor descriptor;
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void Initialize() { } // to force static ctor
static BagDescriptionProvider() {
ICustomTypeDescriptor parent =
TypeDescriptor.GetProvider(typeof(Bag)).GetTypeDescriptor(typeof(Bag));
descriptor = new BagTypeDescriptor(parent);
TypeDescriptor.AddProvider(new BagDescriptionProvider(), typeof(Bag));
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,
object instance) {
return descriptor;
}
internal static void ResetProperties() {
descriptor.ResetProperties();
}
}
sealed class BagTypeDescriptor : CustomTypeDescriptor {
public BagTypeDescriptor(ICustomTypeDescriptor parent)
: base(parent) {
if (parent == null) throw new ArgumentNullException("parent");
}
private PropertyDescriptorCollection properties;
internal void ResetProperties() {
properties = null;
}
public override PropertyDescriptorCollection GetProperties(Attribute[]
attributes) {
return GetProperties();
}
public override PropertyDescriptorCollection GetProperties() {
if (properties == null) {
properties = Bag.GetProperties();
}
return properties;
}
}
[ImmutableObject(true)]
class SimpleContext : ITypeDescriptorContext {
readonly object instance;
readonly PropertyDescriptor property;
internal SimpleContext(object instance, PropertyDescriptor
property) {
this.instance = instance;
this.property = property;
}
public IContainer Container { get { return null; } }
public object Instance { get { return instance; } }
public void OnComponentChanged() { }
public bool OnComponentChanging() { return true; }
public PropertyDescriptor PropertyDescriptor {
get {
return property;
}
}
public object GetService(Type serviceType) { return null; }
}
Personally, that isn't the way I would approach it. I would use a parser
(like on of those already cited) to find something that I can invoke - but
that doesn't necessarily mean compiling. I would use
TypeDescriptionProvider to add a runtime-only PropertyDescriptor into the
mix, and in the GetValue etc I would invoke the above expression. No need
for compiling; no need for dynamic types - just the types you already
defined in your object model. Just smart use of the
"System.ComponentModel" area. And likewise for additional read/write data
properties; put a property-bag data-store[*1] into the EntityBase object
model (Dictionary<string,object> would do), and somewhere (prefereably
static) to keep the track of the extra properties[*2], and just add in
PropertyDescriptors (from the list in [*2]) whose GetValue and SetValue
talk the the instance's property-bag (in [*1]).
The above is a little more complex than some code, but I would expect
(from experience) it to be a lot more robust than messing with compiling
code (especially user-code) and hooking it all together with reflection.
Using your code and hints I created test case below.
Issues:
1. I need that business objects derive from my base class.
How to change this code so that EntityBase<T> class is derived from object,
not form Bag ?
2. How to add dynamic calculated property which returns "Mr "+name as value?
Property value calculation can be built-in. I'm interested is it possible
to add calculated property at all.
3. Will Castle Activerecord/NHiberante lazy mode support this kind of
business objects ?
Lazy mode uses Castle DynamicProxy to catch all propeperties.
4. How to simpify this code? It looks complicated and messy.
5. In RDL report expressions I need to get dynamic property value from
static method like
class DynamicCustomer {
public static string GetName( string id ) {
return ExecScalar("SELECT name FROM customer WHERE id="+id);
}
}
When compiling I create all such methods and Equals() override dynamically.
How to create this and general Equals() methods when properties are added
using TypeDescriptor ?
To use TypeDescriptor I must create replace all GetXXXXXX (where XXXXXX is
property name ) methods
with method with two parameters like
public static string GetStringProperty( string id, string property )
but this is inconvenient to type in report designer ( I need to use hundreds
of such calls in reports).
6. What are [*1] and [*2] in your message?
7. This causes TypeInitializationException in MONO. How to run it in MONO?
Earlier said:Note that this is a *simplified* version of such, as it only supports a
single type.
In production code I would expect Bag to act as a base-class, and as such
a few changes would have to be made to identify the correct (current) type
(rather than using typeof(Bag)).
How to chage this code so that correct type is identified ?
Andrus.
using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
static class Program {
[STAThread]
static void Main() {
Customer Customer = new Customer();
Customer.AddProperty<string>("Name");
Customer.SetValue("Name", "Marc");
MessageBox.Show(Customer.GetStringValue("Name"));
}
}
// Runtime extensible business object
public class Customer : EntityBase<Customer> {
string id;
public string Id {
get {
return id;
}
set {
id = value;
}
}
}
// from core library
public abstract class EntityBase<T> : Bag {
public virtual void SetValue(string propertyName, string propertyValue) {
PropertyDescriptor p =
TypeDescriptor.GetProperties(typeof(T))[propertyName];
ITypeDescriptorContext ctx = new SimpleContext(this, p);
object val;
val = p.Converter.ConvertFromString(ctx, propertyValue);
p.SetValue(this, val);
}
public virtual string GetStringValue(string propertyName) {
PropertyDescriptor p =
TypeDescriptor.GetProperties(typeof(T))[propertyName];
return p.GetValue(this).ToString();
}
}
interface IBag {
void OnAfterValueChanged(string propertyName);
void AddHandler(object key, EventHandler value);
void RemoveHandler(object key, EventHandler value);
void OnEvent(object key);
IBagValue GetValue(string propertyName);
}
interface IBagValue {
void ResetValue();
bool IsDefaultValue { get;}
object Value { get; set;}
event EventHandler ValueChanged;
}
interface IBagDefinition {
IBagValue Create(IBag bag);
PropertyDescriptor Property { get;}
}
sealed class BagDefinition<T> : IBagDefinition {
private readonly PropertyDescriptor property;
public PropertyDescriptor Property { get { return property; } }
private readonly T defaultValue;
public T DefaultValue { get { return defaultValue; } }
public string Name { get { return Property.Name; } }
IBagValue IBagDefinition.Create(IBag bag) {
return new BagValue<T>(bag, this);
}
public BagDefinition(string propertyName, Attribute[] attributes) {
defaultValue = default(T);
if (attributes != null) { // check for a default value
foreach (Attribute attrib in attributes) {
DefaultValueAttribute defAttrib = attrib as
DefaultValueAttribute;
if (defAttrib != null) {
defaultValue = (T)defAttrib.Value;
break;
}
}
}
property = new BagPropertyDescriptor(propertyName, attributes);
}
internal class BagPropertyDescriptor : PropertyDescriptor {
public BagPropertyDescriptor(string name, Attribute[] attributes)
: base(name, attributes) { }
private IBagValue GetBagValue(object component) {
return ((IBag)component).GetValue(Name);
}
public override object GetValue(object component) {
return GetBagValue(component).Value;
}
public override void SetValue(object component, object value) {
GetBagValue(component).Value = value;
}
public override Type ComponentType {
get { return typeof(Bag); }
}
public override Type PropertyType {
get { return typeof(T); }
}
public override bool IsReadOnly {
get { return false; }
}
public override bool CanResetValue(object component) {
return true;
}
public override void ResetValue(object component) {
GetBagValue(component).ResetValue();
}
public override bool ShouldSerializeValue(object component) {
return !GetBagValue(component).IsDefaultValue;
}
public override bool SupportsChangeEvents {
get { return true; }
}
public override void AddValueChanged(object component,
EventHandler handler) {
GetBagValue(component).ValueChanged += handler;
}
public override void RemoveValueChanged(object component,
EventHandler handler) {
GetBagValue(component).ValueChanged -= handler;
}
}
}
sealed class BagValue<T> : IBagValue, ITypeDescriptorContext {
private T value;
private readonly IBag bag;
void IBagValue.ResetValue() {
Value = Definition.DefaultValue;
}
bool IBagValue.IsDefaultValue {
get {
return EqualityComparer<T>.Default.Equals(Value,
Definition.DefaultValue);
}
}
private readonly BagDefinition<T> definition;
public IBag Bag { get { return bag; } }
public BagDefinition<T> Definition { get { return definition; } }
public BagValue(IBag bag, BagDefinition<T> definition) {
if (bag == null) throw new ArgumentNullException("bag");
if (definition == null) throw new ArgumentNullException("definition");
this.bag = bag;
this.definition = definition;
Value = Definition.DefaultValue;
}
public T Value {
get { return value; }
set {
if (EqualityComparer<T>.Default.Equals(Value, value))
return;
this.value = value;
Bag.OnAfterValueChanged(Definition.Name);
Bag.OnEvent(Definition);
}
}
public event EventHandler ValueChanged {
add { Bag.AddHandler(Definition, value); }
remove { Bag.RemoveHandler(Definition, value); }
}
object IBagValue.Value {
get { return Value; }
set { Value = (T)value; }
}
IContainer ITypeDescriptorContext.Container {
get {
return null;
}
}
object ITypeDescriptorContext.Instance { get { return Bag; } }
void ITypeDescriptorContext.OnComponentChanged() {
Bag.OnAfterValueChanged(Definition.Name);
}
bool ITypeDescriptorContext.OnComponentChanging() { return true; }
PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor {
get { return Definition.Property; }
}
object IServiceProvider.GetService(Type serviceType) {
return null;
}
}
public /*sealed*/ class Bag : IBag, INotifyPropertyChanged {
private EventHandlerList events;
void IBag.AddHandler(object key, EventHandler handler) {
AddHandler(key, handler);
}
void IBag.RemoveHandler(object key, EventHandler handler) {
RemoveHandler(key, handler);
}
void IBag.OnEvent(object key) {
OnEvent(key);
}
private void AddHandler(object key, Delegate handler) {
if (handler == null) return;
if (events == null) events = new EventHandlerList();
events.AddHandler(key, handler);
}
private void RemoveHandler(object key, Delegate handler) {
if (events == null || handler == null) return;
events.RemoveHandler(key, handler);
}
private void OnEvent(object key) {
if (events == null) return;
EventHandler handler = events[key] as EventHandler;
if (handler != null) handler(this, EventArgs.Empty);
}
public event PropertyChangedEventHandler PropertyChanged {
add { AddHandler(EVENT_PropertyChanged, value); }
remove { RemoveHandler(EVENT_PropertyChanged, value); }
}
private static readonly object EVENT_PropertyChanged = new object();
private void OnPropertyChanged(string propertyName) {
if (events == null) return;
PropertyChangedEventHandler handler = events[EVENT_PropertyChanged] as
PropertyChangedEventHandler;
if (handler != null) handler(this, new
PropertyChangedEventArgs(propertyName));
}
void IBag.OnAfterValueChanged(string propertyName) {
OnPropertyChanged(propertyName);
}
IBagValue IBag.GetValue(string propertyName) {
return GetValue(propertyName);
}
private readonly Dictionary<string, IBagValue> values =
new Dictionary<string, IBagValue>(StringComparer.InvariantCulture);
private IBagValue GetValue(string propertyName) {
lock (values) {
IBagValue value;
if (!values.TryGetValue(propertyName, out value)) {
value = CreateValue(this, propertyName);
values.Add(propertyName, value);
}
return value;
}
}
static readonly Dictionary<string, IBagDefinition> defintions =
new Dictionary<string,
IBagDefinition>(StringComparer.InvariantCulture);
public static PropertyDescriptor AddProperty<T>(string propertyName,
params Attribute[] attributes) {
BagDefinition<T> def = new BagDefinition<T>(propertyName, attributes);
lock (defintions) {
defintions.Add(propertyName, def);
BagDescriptionProvider.ResetProperties();
}
return def.Property;
}
internal static PropertyDescriptorCollection GetProperties() {
lock (defintions) {
PropertyDescriptor[] props = new PropertyDescriptor[defintions.Count];
int i = 0;
foreach (IBagDefinition def in defintions.Values) {
props[i++] = def.Property;
}
return new PhantomPropertyDescriptorCollection(props, false);
}
}
internal sealed class PhantomPropertyDescriptorCollection :
PropertyDescriptorCollection {
public PhantomPropertyDescriptorCollection(PropertyDescriptor[]
properties, bool readOnly)
: base(properties, readOnly) {
}
public override PropertyDescriptor Find(string name, bool ignoreCase) {
PropertyDescriptor prop = base.Find(name, ignoreCase);
if (prop == null) {
prop = AddProperty<object>(name);
Add(prop);
}
return prop;
}
}
static IBagValue CreateValue(IBag bag, string propertyName) {
lock (defintions) {
return defintions[propertyName].Create(bag);
}
}
public Bag() { }
static Bag() {
BagDescriptionProvider.Initialize();
}
}
sealed class BagDescriptionProvider : TypeDescriptionProvider {
static readonly BagTypeDescriptor descriptor;
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void Initialize() { } // to force static ctor
static BagDescriptionProvider() {
ICustomTypeDescriptor parent =
TypeDescriptor.GetProvider(typeof(Bag)).GetTypeDescriptor(typeof(Bag));
descriptor = new BagTypeDescriptor(parent);
TypeDescriptor.AddProvider(new BagDescriptionProvider(), typeof(Bag));
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,
object instance) {
return descriptor;
}
internal static void ResetProperties() {
descriptor.ResetProperties();
}
}
sealed class BagTypeDescriptor : CustomTypeDescriptor {
public BagTypeDescriptor(ICustomTypeDescriptor parent)
: base(parent) {
if (parent == null) throw new ArgumentNullException("parent");
}
private PropertyDescriptorCollection properties;
internal void ResetProperties() {
properties = null;
}
public override PropertyDescriptorCollection GetProperties(Attribute[]
attributes) {
return GetProperties();
}
public override PropertyDescriptorCollection GetProperties() {
if (properties == null) {
properties = Bag.GetProperties();
}
return properties;
}
}
[ImmutableObject(true)]
class SimpleContext : ITypeDescriptorContext {
readonly object instance;
readonly PropertyDescriptor property;
internal SimpleContext(object instance, PropertyDescriptor
property) {
this.instance = instance;
this.property = property;
}
public IContainer Container { get { return null; } }
public object Instance { get { return instance; } }
public void OnComponentChanged() { }
public bool OnComponentChanging() { return true; }
public PropertyDescriptor PropertyDescriptor {
get {
return property;
}
}
public object GetService(Type serviceType) { return null; }
}