Компилировать метод динамического экземпляра по дереву выражений, с этим, частным и защищенным доступом?

Можно ли создать динамический метод на С# (или, возможно, на других языках .NET) как метод экземпляра уже существующего типа с доступом к ссылке «эта», закрытым и защищенным членам?

Для меня очень важен законный доступ к закрытым/защищенным членам без обхода ограничений видимости, поскольку это возможно с DynamicMethod.

Вызов Expression.Lambda CompileToMethod(MethodBuilder) выглядит для меня очень сложным, и я еще не смог найти способ создать правильный MethodBuilder для уже существующего типа/модуля.

EDIT: теперь я создал копию Action‹DestClass, ISourceClass›, как статический/расширенный метод, из дерева выражений. Доступ к Expression.Property(...) в любом случае определяется Reflection (PropertyInfo), и я могу получить доступ к закрытым/защищенным членам, если они определены через Reflection. Не так хорошо, как с DynamicMethod и сгенерированным IL, где сгенерированный метод ведет себя как член с проверкой видимости (и даже немного быстрее, чем обычный код копирования C#), но деревья выражений кажутся намного лучше в обслуживании.

Вот так при работе с DynamicMethod и Reflection.Emit:

public static DynamicMethod GetDynamicCopyValuesMethod()
{
    var dynamicMethod = new DynamicMethod(
        "DynLoad",
        null, // return value type (here: void)
        new[] { typeof(DestClass), typeof(ISourceClass) }, 
            // par1: instance (this), par2: method parameter
        typeof(DestClass)); 
            // class type, not Module reference, to access private properties.

        // generate IL here
        // ...
}

// class where to add dynamic instance method   

public class DestClass
{
    internal delegate void CopySourceDestValuesDelegate(ISourceClass source);

    private static readonly DynamicMethod _dynLoadMethod = 
        DynamicMethodsBuilder.GetDynamicIlLoadMethod();

    private readonly CopySourceDestValuesDelegate _copySourceValuesDynamic;

    public DestClass(ISourceClass valuesSource) // constructor
    {
        _valuesSource = valuesSource;
        _copySourceValuesDynamic = 
            (LoadValuesDelegate)_dynLoadMethod.CreateDelegate(
                typeof(CopySourceDestValuesDelegate), this);
                // important: this as first parameter!
    }

    public void CopyValuesFromSource()
    {
        copySourceValuesDynamic(_valuesSource); // call dynamic method
    }

    // to be copied from ISourceClass instance
    public int IntValue { get; set; } 

    // more properties to get values from ISourceClass...
}

Этот динамический метод может получить доступ к закрытым/защищенным членам DestClass с полной проверкой видимости.

Есть ли эквивалент при компиляции дерева выражений?


person Erik Hart    schedule 29.08.2016    source источник
comment
Вы не можете изменить исходный код существующего типа, если это то, что вы хотите сделать. Вы можете создать только метод-расширение, однако это будет не метод экземпляра для этого типа, а статический метод в другом классе. Доступ к внутренностям типа может представлять угрозу безопасности, не так ли?   -  person HimBromBeere    schedule 29.08.2016
comment
Таким образом, вы обходите ограничения видимости. Вы получаете доступ к полям, к которым раньше мог получить доступ только автор этого класса. Деревья выражений поддерживают частные члены, так почему бы не использовать это? lambda.Compile(). Вы можете использовать this, не будучи методом экземпляра. this - это просто скрытый параметр.   -  person usr    schedule 29.08.2016
comment
@usr: приведенный выше пример IL разрешает частный/защищенный доступ в IL только в том случае, если я укажу typeof(DestClass) в конструкторе DynamicMethod, а не в том случае, если я использую перегрузку с модулем. Также есть еще одна перегрузка с флагом skipVisibility, которая, как я читал, отключает проверку, в то время как используемый мной конструктор сохраняет проверку видимости, но обрабатывает IL как часть DestClass. Я не знаю о внутренностях, но вот как это выглядит, и что я хотел бы сейчас использовать деревья выражений (я также проверю, всегда ли они могут получить доступ к приватным/защищенным).   -  person Erik Hart    schedule 29.08.2016
comment
Весь вариант использования выглядит странно для меня. Если вы можете уточнить, чего вы на самом деле пытаетесь достичь, возможно, кто-то сможет дать вам лучшее решение.   -  person Serge Semenov    schedule 29.08.2016
comment
Это копирование данных между двумя разными уровнями программного обеспечения. Код был реализован давно, с использованием атрибутов для определения, из каких свойств копировать значения, включая объявления и точки входа для конверсий и специальный код. Как [Source(typeof(ISourceClass), "SourceIntValue")]. На данный момент он анализирует атрибуты, а затем копирует значения путем отражения при каждой операции копирования. Производительность принесена в жертву удобству. Этого было достаточно в течение долгого времени, но теперь огромная потеря производительности становится проблемой.   -  person Erik Hart    schedule 29.08.2016
comment
Можете ли вы расширить ISourceClass?   -  person Serge Semenov    schedule 29.08.2016
comment
Код/метод копирования находится в целевом классе, и он должен принимать любой исходный класс - моя ошибка: он даже не указывал typeof(ISourceClass), а просто отражал свойства любого типа исходного класса по имени ( как строка). Однако эта чрезвычайная гибкость больше не нужна (и, вероятно, никогда не была).   -  person Erik Hart    schedule 29.08.2016
comment
Как вы сопоставляете свойства/поля для копирования? По имени? Что делать, если тип значения не совпадает?   -  person Serge Semenov    schedule 29.08.2016
comment
Давайте продолжим обсуждение в чате.   -  person Serge Semenov    schedule 29.08.2016


Ответы (1)


Я делал это много раз, поэтому вы можете легко получить доступ к любому защищенному члену типа с помощью такого кода:

static Action<object, object> CompileCopyMembersAction(Type sourceType, Type destinationType)
{
    // Action input args: void Copy(object sourceObj, object destinationObj)
    var sourceObj = Expression.Parameter(typeof(object));
    var destinationObj = Expression.Parameter(typeof(object));

    var source = Expression.Variable(sourceType);
    var destination = Expression.Variable(destinationType);

    var bodyVariables = new List<ParameterExpression>
    {
        // Declare variables:
        // TSource source;
        // TDestination destination;
        source,
        destination
    };

    var bodyStatements = new List<Expression>
    {
        // Convert input args to needed types:
        // source = (TSource)sourceObj;
        // destination = (TDestination)destinationObj;
        Expression.Assign(source, Expression.ConvertChecked(sourceObj, sourceType)),
        Expression.Assign(destination, Expression.ConvertChecked(destinationObj, destinationType))
    };

    // TODO 1: Use reflection to go through TSource and TDestination,
    // find their members (fields and properties), and make matches.
    Dictionary<MemberInfo, MemberInfo> membersToCopyMap = null;

    foreach (var pair in membersToCopyMap)
    {
        var sourceMember = pair.Key;
        var destinationMember = pair.Value;

        // This gives access: source.MyFieldOrProperty
        Expression valueToCopy = Expression.MakeMemberAccess(source, sourceMember);

        // TODO 2: You can call a function that converts source member value type to destination's one if they don't match:
        // valueToCopy = Expression.Call(myConversionFunctionMethodInfo, valueToCopy);

        // TODO 3: Additionally you can call IClonable.Clone on the valueToCopy if it implements such interface.
        // Code: source.MyFieldOrProperty == null ? source.MyFieldOrProperty : (TMemberValue)((ICloneable)source.MyFieldOrProperty).Clone()
        //if (typeof(ICloneable).IsAssignableFrom(valueToCopy.Type))
        //    valueToCopy = Expression.IfThenElse(
        //        test: Expression.Equal(valueToCopy, Expression.Constant(null, valueToCopy.Type)),
        //        ifTrue: valueToCopy,
        //        ifFalse: Expression.Convert(Expression.Call(Expression.Convert(valueToCopy, typeof(ICloneable)), typeof(ICloneable).GetMethod(nameof(ICloneable.Clone))), valueToCopy.Type));

        // destination.MyFieldOrProperty = source.MyFieldOrProperty;
        bodyStatements.Add(Expression.Assign(Expression.MakeMemberAccess(destination, destinationMember), valueToCopy));
    }

    // The last statement in a function is: return true;
    // This is needed, because LambdaExpression cannot compile an Action<>, it can do Func<> only,
    // so the result of a compiled function does not matter - it can be any constant.
    bodyStatements.Add(Expression.Constant(true));

    var lambda = Expression.Lambda(Expression.Block(bodyVariables, bodyStatements), sourceObj, destinationObj);
    var func = (Func<object, object, bool>)lambda.Compile();

    // Decorate Func with Action, because we don't need any result
    return (src, dst) => func(src, dst);
}

Это скомпилирует действие, которое копирует элементы из одного объекта в другой (см. список TODO).

person Serge Semenov    schedule 30.08.2016