3

Is it possible to create a dynamic method in C# (or possibly other .NET languages) as an instance method of an already existing type, with access to "this" reference, private and protected members?

A legitimate access to private/protected members, without circumventing visibility limits, is quite important for me, as it is possible with DynamicMethod.

The Expression.Lambda CompileToMethod(MethodBuilder) call looks very complicated for me, and I could not yet find a way to create a proper MethodBuilder for an already existing type/module

EDIT: I now created a copy Action<DestClass, ISourceClass>, like a static/extension method, from an Expression tree. Expression.Property(...) access is defined by Reflection (PropertyInfo) anyway, and I can access private/protected members, if defined through Reflection. Not as nice as with DynamicMethod and emitted IL, where the generated method behaves like a member with visibility checks (and is even a bit faster than ordinary C# copy code), but Expression trees seem to be far better to maintain.

Like this, when working with DynamicMethod and 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...
}

This dynamic method can access DestClass private/protected members with full visibility checks.

Is there any equivalent when compiling an Expression tree?

Erik Hart
  • 1,114
  • 1
  • 13
  • 28
  • You can´t modify the source-code of an existing type if this is what you want to do. You could only create an extension-method, however it won´t be an instance method on that type but a static method within another class. Accessing the internals of a type might be a security-risk, wouldn´t it? – MakePeaceGreatAgain Aug 29 '16 at 13:31
  • Well, you *are* circumventing visibility limits this way. You become able to access fields that previously only the author of that class could access. Expression trees support private members so why not use that? `lambda.Compile()`. You can use `this` without being an instance method. `this` is just a hidden parameter. – usr Aug 29 '16 at 13:33
  • @usr: The above IL example only allows private/protected access in IL if I specify the typeof(DestClass) in the DynamicMethod constructor, not if I use the overload with Module. There's also another overload with a "skipVisibility" flag, which, according to my reading, disables checking, while my used constructor keeps visibility check, but treats the IL as part of DestClass. I don't know about the internals, but this is what it looks like, and what I would now like using Expression trees (I will also check if they can always access private/protected). – Erik Hart Aug 29 '16 at 14:26
  • 2
    The whole use-case looks bizarre to me. If you can elaborate on what you are actually trying to achieve, maybe somebody can give you a better solution. – Serge Semenov Aug 29 '16 at 15:01
  • It is to copy data between two different layers of software. The code has been implemented long ago, using attributes to define from which properties to copy the values, including declarations and entry points for conversions and special code. Like `[Source(typeof(ISourceClass), "SourceIntValue")]`. For now, it is parsing the attributes and then copying the values by reflection with every copy operation. Performance sacrificed for convenience. It was sufficient for a long time, but now the huge performance penalty becomes a problem. – Erik Hart Aug 29 '16 at 17:40
  • Can you expand the `ISourceClass`? – Serge Semenov Aug 29 '16 at 19:52
  • The copy code / method is in the destination class, and it's supposed to take any kind of source class - my mistake: it didn't even specify the typeof(ISourceClass), but simply reflected the properties of any source class type by name (as string). However, this extreme flexibility is no longer needed (and probably never was). – Erik Hart Aug 29 '16 at 20:04
  • How do you map properties/fields to copy? By name? What if value type does not match? – Serge Semenov Aug 29 '16 at 20:11
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/122143/discussion-between-serge-semenov-and-erik-hart). – Serge Semenov Aug 29 '16 at 20:38

1 Answers1

1

I've done this many times, so you can easily access any protected member of a type with such code:

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

This will compile an action which copies members from one object to another (see TODO list though).

Serge Semenov
  • 9,232
  • 3
  • 23
  • 24