Reputation: 21
I am working on a caching solution to get all property fields of a class, create getter and setter delegates out of them, and store them.
This is how I do it at the moment:
// entity is a class with a single public property called 'Name'
var entity = new Entity("Cat");
var nameProp = typeof(Entity)
.GetProperties()
.Single(x => x.Name == nameof(Entity.Name));
// getting getter and setter methods
var namePropGetter = nameProp.GetGetMethod();
var namePropSetter = nameProp.GetSetMethod();
// creating delegates for getter and setter methods
var namePropGetterDelegate = (Func<Entity, string>)Delegate.CreateDelegate(typeof(Func<Entity, string>), namePropGetter);
var namePropSetterDelegate = (Action<Entity, string>)Delegate.CreateDelegate(typeof(Action<Entity, string>), namePropSetter);
All getters are to be stored in one Dictionary and all setters are to be stored in another. This would let me quickly get or set values of an object rather than using a traditional PropertyField.GetValue()
or PropertyField.SetValue()
.
The only problem is the storage of getter and setter delegates.
I tried storing all getters into Func<TargetClass, object>
and setters into Action<TargetClass, object>
like so:
var getterDelegate = (Func<Entity, object>)Delegate.CreateDelegate(typeof(Func<Entity, object>), namePropGetter);
var setterDelegate = (Action<Entity, object>)Delegate.CreateDelegate(typeof(Action<Entity, object>), namePropSetter);
But it would give me the following error:
Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.
Storing everything inside of Delegate
would force me to use DynamicInvoke()
, which defeats the purpose of caching everything in the first place because it's slow.
Upvotes: 1
Views: 1173
Reputation: 29222
I don't think you need to mess with Delegate
at all. You can store a collection of Delegate
, but that doesn't do any good if you have to somehow know the "real" type of each delegate so you can cast it. That tends to run into a wall. It doesn't make sense to store a collection of some common type if, in order to use each one, you have to already know its underlying type. (If we're just going to cast to the expected type we could use Dictionary<string, object>
.)
A dictionary of getters with the property name as the key would look like this:
var getters = Dictionary<string, Func<Entity, object>>();
To store the getter for a property (PropertyInfo
discovered by reflection)
getters.Add(property.Name, (entity) => property.GetValue(entity));
Storing setters is more complicated. You could store them like this:
var setters = new Dictionary<string, Action<Entity, object>>();
...and add a property:
setters.Add(property.Name, (entity, value) => property.SetValue(entity, value);
But suppose you have an entity, a property name, and a value you want to set on that property. You can retrieve the Action
from the dictionary, but without some other controls it's not certain that the value you intend to set is the right type. We're really back to the same problem. We're storing items as a "least common denominator" type in a collection, but in order to use them we have to know the real type. The benefit of polymorphism is when the common denominator is all we need to know.
Maybe you've already got it determined how you would keep that second part straight, but there's a good chance that once you have the dictionary you'll have a difficult time using what's in it. In that case it might be necessary to take a step back and see if there's another way to solve the larger problem that doesn't involve this approach.
Upvotes: 0
Reputation: 8498
The error happens because you pass an incorrect type to Delegate.CreateDelegate
. For instance, for the getter of an Int32
property you should pass Func<Entity, Int32>
and not Func<Entity, object>
. This can be achieved with typeof(Func<,>).MakeGenericType(Entity, propType)
.
If you want to have a dictionary of delegates per property name, you should use Dictionary<string, Delegate>
, then cast Delegate
to a specific delegate type before invoking it:
var gettersDictionary = new Dictionary<string, Delegate>();
var settersDictionary = new Dictionary<string, Delegate>();
foreach (var prop in typeof(Entity).GetProperties())
{
var getterDelegate = Delegate.CreateDelegate(
typeof(Func<,>).MakeGenericType(typeof(Entity), prop.PropertyType),
prop.GetGetMethod());
gettersDictionary.Add(prop.Name, getterDelegate);
var setterDelegate = Delegate.CreateDelegate(
typeof(Action<,>).MakeGenericType(typeof(Entity), prop.PropertyType),
prop.GetSetMethod());
settersDictionary.Add(prop.Name, setterDelegate);
}
T GetPropValue<T>(Entity entity, string name)
{
var getter = (Func<Entity, T>) gettersDictionary[name];
return getter(entity);
}
void SetPropValue<T>(Entity entity, string name, T value)
{
var setter = (Action<Entity, T>) settersDictionary[name];
setter(entity, value);
}
Upvotes: 1