Создайте делегата из метода получения или установки свойства
Чтобы создать делегат из метода, вы можете использовать синтаксис безопасного типа:
private int Method() { ... }
// and create the delegate to Method...
Func<int> d = Method;
Свойство - это оболочка для метода получения и установки, и я хочу создать делегат для метода получения свойства. Что-то вроде
public int Prop { get; set; }
Func<int> d = Prop;
// or...
Func<int> d = Prop_get;
Который не работает, к сожалению. Я должен создать отдельный лямбда-метод, который кажется ненужным, когда метод get в любом случае совпадает с сигнатурой делегата:
Func<int> d = () => Prop;
Чтобы использовать метод делегата напрямую, я должен использовать мерзкое отражение, которое не безопасно для компиляции:
// something like this, not tested...
MethodInfo m = GetType().GetProperty("Prop").GetGetMethod();
Func<int> d = (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), m);
Есть ли способ создания делегата в методе получения свойства непосредственно безопасным для компиляции способом, аналогичным созданию делегата в обычном методе в верхней части, без необходимости использования промежуточного лямбда-метода?
3 ответа
Насколько я могу сказать, вы уже записали все "действительные" варианты. Поскольку невозможно явно обратиться к получателю или установщику в обычном коде (то есть без отражения), я не думаю, что есть способ сделать то, что вы хотите.
Потратив несколько часов на то, чтобы разобраться в этом, вот решение для тех случаев, когда вам нужно быстро получить доступ к собственности из другого класса. Например, если вам нужно написать кэшированную карту свойств для ранее неизвестных классов, у которых нет знаний об этой магии CreateDelegate.
Простой невинный класс данных, такой как этот:
public class DataClass
{
public int SomeProp { get; set; }
public DataClass(int value) => SomeProp = value;
}
Класс универсального средства доступа, где T1 - это тип класса, который содержит свойство, а T2 - это тип этого свойства, выглядит так:
public class PropAccessor<T1, T2>
{
public readonly Func<T1, T2> Get;
public readonly Action<T1, T2> Set;
public PropAccessor(string propName)
{
Type t = typeof(T1);
MethodInfo getter = t.GetMethod("get_" + propName);
MethodInfo setter = t.GetMethod("set_" + propName);
Get = (Func<T1, T2>)Delegate.CreateDelegate(typeof(Func<T1, T2>), null, getter);
Set = (Action<T1, T2>)Delegate.CreateDelegate(typeof(Action<T1, T2>), null, setter);
}
}
И тогда вы можете сделать:
var data = new DataClass(100);
var accessor = new PropAccessor<DataClass, int>("SomeProp");
log(accessor.Get(data));
accessor.Set(data, 200);
log(accessor.Get(data));
По сути, вы можете обойти ваши классы с отражением при запуске и создать кэш PropAccessors для каждого свойства, предоставляя вам достаточно быстрый доступ.
Редактировать: еще несколько часов спустя..
Закончилось чем-то вроде этого. Абстрактный предок PropAccessor был необходим, чтобы я мог фактически объявить поле этого типа в классе Prop, не прибегая к использованию динамического. Завершается примерно в 10 раз быстрее, чем с MethodInfo.Invoke для геттеров и сеттеров.
internal abstract class Accessor
{
public abstract void MakeAccessors(PropertyInfo pi);
public abstract object Get(object obj);
public abstract void Set(object obj, object value);
}
internal class PropAccessor<T1, T2> : Accessor
{
private Func<T1, T2> _get;
private Action<T1, T2> _set;
public override object Get(object obj) => _get((T1)obj);
public override void Set(object obj, object value) => _set((T1)obj, (T2)value);
public PropAccessor() { }
public override void MakeAccessors(PropertyInfo pi)
{
_get = (Func<T1, T2>)Delegate.CreateDelegate(typeof(Func<T1, T2>), null, pi.GetMethod);
_set = (Action<T1, T2>)Delegate.CreateDelegate(typeof(Action<T1, T2>), null, pi.SetMethod);
}
}
internal class Prop
{
public string name;
public int length;
public int offset;
public PropType type;
public Accessor accessor;
}
internal class PropMap
{
public UInt16 length;
public List<Prop> props;
internal PropMap()
{
length = 0;
props = new List<Prop>();
}
internal Prop Add(PropType propType, UInt16 size, PropertyInfo propInfo)
{
Prop p = new Prop()
{
name = propInfo.Name,
length = size,
offset = this.length,
type = propType,
Encode = encoder,
Decode = decoder,
};
Type accessorType = typeof(PropAccessor<,>).MakeGenericType(propInfo.DeclaringType, propInfo.PropertyType);
p.accessor = (Accessor)Activator.CreateInstance(accessorType);
p.accessor.MakeAccessors(propInfo);
this.length += size;
props.Add(p);
return p;
}
}
Хитрость в том, что Property
на самом деле это просто фасад фактических методов получения и / или установки, которые скрыты. Компилятор испускает эти методы и называет их в соответствии с именем Property
с префиксом get_ и set_ соответственно. В приведенном ниже примере это будет int get_Value()
а также void set_Value(int)
, Так что просто обойдите так называемое "свойство" и просто идите прямо к этим методам.
С помощью метода get и / или setter у нас есть два варианта.
Мы можем создать связанный делегат, который имеет
this
значение для некоторого экземпляра "сгорел". Это похоже на то, что вы ожидаете от самого свойства, т. Е. Этот делегат будет полезен только для доступа к этому одному экземпляру среды выполнения. Преимущество состоит в том, что, поскольку делегат постоянно привязан к своему экземпляру, вам не нужно передавать дополнительный аргумент.Другой вариант - создать делегатов, которые не связаны с конкретным целевым экземпляром. Хотя они вызывают те же методы доступа к свойствам, что и раньше, в этом случае
Target
свойство самого делегата является пустым / нулевым. Не хватает любогоthis
Если указатель используется, сигнатура метода для несвязанного делегата изменяется, чтобы показать знаменитый указатель " скрыто это ".
Дальнейшее обсуждение ниже, но сначала вот код. Он иллюстрирует все четыре случая: getter/setter -vs- bound/unbound.
partial class Cls
{
static Cls()
{
UnboundGet = create<Func<Cls, int>>(null, mi_get);
UnboundSet = create<Action<Cls, int>>(null, mi_set);
}
public Cls()
{
BoundGet = create<Func<int>>(this, mi_get);
BoundSet = create<Action<int>>(this, mi_set);
}
public readonly static Func<Cls, int> UnboundGet;
public readonly static Action<Cls, int> UnboundSet;
public readonly Func<int> BoundGet;
public readonly Action<int> BoundSet;
public int Value { get; set; }
};
нб, это относится к некоторому вспомогательному коду, который включен в конец этого поста
Подводя итог, можно сказать, что "истинная подпись" метода экземпляра идентична связанному случаю делегата, но отменяется. Связанные делегаты позаботятся о том, чтобы предоставить его в качестве первого аргумента, предоставив экземпляр, который они носят в этом Target
имущество. Несвязанные делегаты универсальны, поэтому вам не нужно больше, чем одна пара геттер / сеттер для каждого свойства. Их можно использовать для доступа к этому свойству экземпляра в любом прошлом, настоящем или будущем экземпляре среды выполнения, но это означает, что вам нужно явно передать желаемую цель this
объект в качестве первого аргумента каждый раз, когда вы вызываете метод получения / установки.
Также обратите внимание, что даже если здесь несвязанные делегаты обращаются к свойствам или методам экземпляра, вам на самом деле не нужен жизнеспособный экземпляр Cls
создать делегата.
Вот демо.
static class demo
{
static demo()
{
var c1 = new Cls { Value = 111 };
var c2 = new Cls { Value = 222 };
Console.WriteLine("c1: {0} c2: {1}", c1, c2);
c1.BoundSet(c1.Value + 444);
Cls.UnboundSet(c2, c2.BoundGet() + 444);
Console.WriteLine("c1: {0} c2: {1}", c1, c2);
}
};
И вывод:
с1: 111 111 111 с2: 222 222 222 с1: 555 555 555 с2: 666 666 666
И наконец, вот некоторые вспомогательные материалы, которые я положил здесь, чтобы уменьшить беспорядок. Обратите внимание, что MethodInfo
Их можно кэшировать и использовать повторно, если вы планируете создавать множество связанных делегатов. Если вместо этого вы предпочитаете использовать несвязанные (статические) делегаты, вам не нужно их хранить; Поскольку несвязанные делегаты работают универсально для любого экземпляра, вы можете решить, что вам никогда не нужно создавать никаких связанных делегатов.
partial class Cls
{
static MethodInfo mi_get = typeof(Cls).GetMethod("get_Value"),
mi_set = typeof(Cls).GetMethod("set_Value");
static T create<T>(Object _this, MethodInfo mi) =>
(T)(Object)Delegate.CreateDelegate(typeof(T), _this, mi);
public override String ToString() =>
String.Format("{0} {1} {2}", Value, BoundGet(), Cls.UnboundGet(this));
}
Другой вариант (в.NET 3.0 и новее) будет использовать DependencyProperty
вместо традиционной собственности. Тогда вы можете обойти DependencyProperty
объект (вместо передачи делегата) и вызов GetValue()
или же SetValue()
при необходимости.
(Да, я знаю, что это старый вопрос, но это был один из главных постов, когда я пытался сделать что-то очень похожее.)