Вот довольно простое решение вашей проблемы, просто кеширующее Getters / Setters per Type.
public static class CachedPropertyAccessUtilsFactory
{
/*
* Convenience Factory to avoid creating instances of
* CachedPropertyAccessUtils by reflection
*/
public static CachedPropertyAccessUtils<TWrapped> Create<TWrapped>(
TWrapped instance)
{
return new CachedPropertyAccessUtils<TWrapped>(instance);
}
}
public class CachedPropertyAccessUtils<TWrapped>
{
private readonly TWrapped _instance;
public CachedPropertyAccessUtils(TWrapped instance)
{
_instance = instance;
}
public GetSetWrapper<TProperty> Property<TProperty>(string propertyName)
{
return new GetSetWrapper<TProperty>(_instance, propertyName);
}
public class GetSetWrapper<TProperty>
{
/*
* Caches generated getters/setters by property name.
* Since this field is static it is shared between all instances with
* identical TWrapped and TProperty.
*/
private static readonly ConcurrentDictionary<string, GetterAndSetterTuple> GettersAndSettersByPropertyName
= new ConcurrentDictionary<string, GetterAndSetterTuple>();
private readonly TWrapped _instance;
private readonly string _propertyName;
public GetSetWrapper(TWrapped instance, string propertyName)
{
_instance = instance;
_propertyName = propertyName;
// Create a Getter/Setter pair if none has been generated previously
GettersAndSettersByPropertyName.GetOrAdd(propertyName, (ignored) => new GetterAndSetterTuple() {
Getter = (Func<TWrapped, TProperty>)Delegate
.CreateDelegate(typeof(Func<TWrapped, TProperty>),
null,
typeof(TWrapped)
.GetProperty(propertyName)
.GetGetMethod()),
Setter = (Action<TWrapped, TProperty>)Delegate
.CreateDelegate(typeof(Action<TWrapped, TProperty>),
null,
typeof(TWrapped)
.GetProperty(propertyName)
.GetSetMethod())
});
}
public TProperty GetValue()
{
return GettersAndSettersByPropertyName[_propertyName].Getter(_instance);
}
public GetSetWrapper<TProperty> SetValue(TProperty value)
{
GettersAndSettersByPropertyName[_propertyName].Setter(_instance, value);
return this;
}
class GetterAndSetterTuple
{
public Func <TWrapped, TProperty> Getter { get; set; }
public Action<TWrapped, TProperty> Setter { get; set; }
}
}
}
Пример использования:
var myInstance = SomeCodeToCreateATypeAtRuntimeAndCreateAnInstanceOfIt();
var wrappedInstance = CachedPropertyAccessUtilsFactory.Create(myInstance);
// The first call to Property() will generate the corresponding Getter/Setter
wrappedInstance.Property<int>("Property1").SetValue(99);
// Subsequent calls will use the cached Getter/Setter
wrappedInstance.Property<int>("Property1").GetValue(); // => 99
// The property can be conveniently held on to:
var property1 = wrappedInstance.Property<int>("Property1");
property1.SetValue(-1);
property1.GetValue(); // => -1
Все это, конечно, предполагает, что вы знаететипы свойств во время выполнения, так что вы можете switch
в нужном Property<TProperty>()
вызове.
Если у вас нет этой информации, можно добавить еще один уровень косвенности, сопоставив string propertyName
с соответствующим свойством наобернутый тип отражением и кэшированием результата поиска.
В этом случае возвращаемый GetSetWrapper
должен, конечно, поддерживать GetValue
/ SetValue
с object
в качестве типа возврата / аргумента, который будет включатьнемного кастинга / бокса назад и вперед за кулисами.