Подтвердить что ты не робот

Создайте делегат из метода getter или setter

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

Хитрость заключается в том, что 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));
}

Ответ 3

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

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

Ответ 4

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