Создайте делегата из метода получения или установки свойства - PullRequest
19 голосов
/ 12 апреля 2010

Чтобы создать делегата из метода, вы можете использовать синтаксис безопасного типа для компиляции:

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);

Есть ли способ создания делегата в методе получения свойства непосредственно безопасным для компиляции способом, аналогичным созданию делегата для обычного метода в верхней части, без необходимости использования промежуточного лямбда-метода?

Ответы [ 4 ]

7 голосов
/ 12 апреля 2010

Насколько я могу сказать, вы уже записали все "действительные" варианты. Поскольку невозможно явно обратиться к получателю или установщику в обычном коде (то есть без отражения), я не думаю, что есть способ сделать то, что вы хотите.

1 голос
/ 01 апреля 2018

Хитрость в том, что Property - это на самом деле просто фасад фактических методов получения и / или установки, которые скрыты. Компилятор испускает эти методы и присваивает им имена в соответствии с именем Property с добавлением get_ и set_ соответственно. В приведенном ниже примере это будут int get_Value() и void set_Value(int). Так что просто обойдите так называемое «свойство» и просто перейдите прямо к этим методам.

При использовании метода получения и / или установки у нас есть два варианта.

  • Мы можем создать связанный делегат , который имеет значение 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; }
};

n.b., Это относится к некоторому вспомогательному коду, который включен внизу этого поста

Подводя итог, можно сказать, что "истинная подпись" метода экземпляра идентична связанному случаю делегата, но отменяется. Связанные делегаты позаботятся о том, чтобы предоставить его в качестве первого аргумента, предоставляя экземпляр, который они переносят в этом свойстве Target. Несвязанные делегаты универсальны, поэтому вам не нужно больше, чем просто одна пара получателей / сеттеров для каждого свойства Их можно использовать для доступа к этому свойству экземпляра в любом прошлом, настоящем или будущем экземпляре среды выполнения, но это означает, что вам нужно явно передавать желаемый целевой объект this в качестве первого аргумента каждый раз, когда вы вызвать геттер / сеттер.

Обратите также внимание, что даже если здесь несвязанные делегаты обращаются к свойствам или методам instance , вам фактически не нужен жизнеспособный экземпляр времени выполнения 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));
}
0 голосов
/ 24 мая 2019

Потратив несколько часов на то, чтобы разобраться в этом, вот решение для тех случаев, когда вам нужно создать быстрый метод доступа к собственности из другого класса. Например, если вам нужно написать кэшированную карту свойств для ранее неизвестных классов, у которых нет знаний об этой магии 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;
    }
}
0 голосов
/ 29 сентября 2015

Другой вариант (в .NET 3.0 и новее) - использовать DependencyProperty вместо традиционного свойства. Затем вы можете обойти объект DependencyProperty (вместо передачи делегата) и при необходимости вызвать GetValue() или SetValue().

(Да, я знаю, что это старый вопрос, но это был один из главных постов, когда я пытался сделать что-то очень похожее.)

...