Ответ 1
Насколько я могу судить, вы уже записали все "действительные" варианты. Так как невозможно явно адресовать геттер или сеттер в нормальном коде (без отражения, то есть), я не думаю, что есть способ сделать то, что вы хотите.
Чтобы создать делегат из метода, вы можете использовать синтаксис типа типа компиляции:
private int Method() { ... }
// and create the delegate to Method...
Func<int> d = Method;
Свойство является оберткой вокруг метода getter и setter, и я хочу создать делегат для метода getter свойства. Что-то вроде
public int Prop { get; set; }
Func<int> d = Prop;
// or...
Func<int> d = Prop_get;
Что, к сожалению, не работает. Я должен создать отдельный лямбда-метод, который кажется ненужным, когда метод getter все равно совпадает с подписью делегата:
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);
Есть ли способ создания делегата в методе получения свойств непосредственно скомпилированным способом, аналогично созданию делегата на обычном методе вверху, без использования промежуточного метода лямбда?
Насколько я могу судить, вы уже записали все "действительные" варианты. Так как невозможно явно адресовать геттер или сеттер в нормальном коде (без отражения, то есть), я не думаю, что есть способ сделать то, что вы хотите.
Хитрость заключается в том, что 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);
}
};
И вывод:
c1: 111 111 111 c2: 222 222 222 c1: 555 555 555 c2: 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()
при необходимости.
(Да, я знаю, что это старый вопрос, но это был один из лучших сообщений, когда я пытался сделать что-то очень похожее.)
Потратив несколько часов на то, чтобы разобраться в этом, вот решение для тех случаев, когда вам нужно быстро получить доступ к собственности из другого класса. Например, если вам нужно написать кэшированную карту свойств для ранее неизвестных классов, у которых нет знаний об этой магии 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;
}
}