【问题标题】:Creating a DynamicMethod to set a readonly field on any type创建 DynamicMethod 以在任何类型上设置只读字段
【发布时间】:2017-04-26 23:56:17
【问题描述】:

我的目标是在运行时创建一个委托,它可以将任何引用类型中的任何字段(包括readonly)设置为用户指定的值。不幸的是,当包含类型的程序集指定 [AllowPartiallyTrustedCallers] 属性时,我当前的实现会在运行时抛出 VerificationException

AssemblyOne:

[assembly: AllowPartiallyTrustedCallers]
public class TypeOne
{
    public TypeOne(TypeTwo typeTwoField)
    {
        this.TypeTwoField = typeTwoField;
    }

    public TypeTwo TypeTwoField { get; }
}

AssemblyTwo:

[assembly: AllowPartiallyTrustedCallers]
public class TypeTwo
{
    public TypeTwo(int i)
    {
        this.Int = i;
    }

    public int Int { get; }
}

Main:

using System;
using System.Reflection;
using System.Reflection.Emit;
using AssemblyOne;
using AssemblyTwo;

namespace Main
{
    class Program
    {
        public class MyType
        {
            public MyType(TypeOne typeOneField)
            {
                this.TypeOneField = typeOneField;
            }

            public TypeOne TypeOneField { get; }
        }

        static void Main(string[] args)
        {
            var fieldInfo = typeof(TypeOne)
                .GetTypeInfo()
                .GetField(
                    "<TypeTwoField>k__BackingField",
                    BindingFlags.Instance | BindingFlags.NonPublic |
                    BindingFlags.Public);
            var setter = (Action<TypeOne, TypeTwo>) GetReferenceSetter(fieldInfo);
            var myType = new MyType(new TypeOne(new TypeTwo(1)));

            // Throws VerificationException
            setter(myType.TypeOneField, new TypeTwo(2));
        }

        public static Delegate GetReferenceSetter(FieldInfo field)
        {
            var delegateType = typeof(Action<,>)
                .MakeGenericType(field.DeclaringType, field.FieldType);

            var method = new DynamicMethod(
                field.Name + "Set",
                null,
                new[] {field.DeclaringType, field.FieldType},
                field.DeclaringType,
                skipVisibility: true);

            var emitter = method.GetILGenerator();
            emitter.Emit(OpCodes.Ldarg_0);
            emitter.Emit(OpCodes.Ldarg_1);
            emitter.Emit(OpCodes.Stfld, field);
            emitter.Emit(OpCodes.Ret);

            return method.CreateDelegate(delegateType);
        }
    }
}

所以MyType 有一个TypeOne,它有一个只读的TypeTwo。在这种情况下,DynamicMethod 在运行时会抛出 VerificationException

是否有可能创建这样一个委托,它适用于您抛出的任何声明类型 + 字段类型?如果有,怎么做?

我意识到 readonly 字段不应该在构造后设置,但这样做的目的是为了反序列化和深度复制。

【问题讨论】:

  • 确认stfldldflda 在这种情况下都不起作用。您是否愿意接受更多“骇人听闻”的方法?顺便说一句,readonly 字段可能会缓存在应用程序中,这意味着您设置的值可能不会立即可见。
  • 我对任何方法都持开放态度。这是为了反序列化和深度复制,所以对象刚刚被构造,没有外部代码有机会缓存​​该值。
  • 如果我的解决方案有效,请发送反馈。
  • 会的,谢谢@IllidanS4 :) SharpUtils 可以作为 NuGet 包提供吗?没找到
  • 很遗憾,没有,但是您可以将相关方法复制到您的代码中。 FromMethodInfo 只是获取方法的返回类型和参数类型的简写,NewCustomDelegateType 是对内部.NET 方法的调用。

标签: c# cil reflection.emit dynamicmethod


【解决方案1】:

动态方法在修改其安全性方面非常有限。我怀疑使用 AssemblyBuilder 可能会绕过安全检查,但我没有尝试过。

相反,可能能够实现这一点的是使用其他访问字段的方法,在 TypedReference 类型中。

public static unsafe void SetValue<T>(object inst, FieldInfo fi, T val)
{
    var mi = typeof(TypedReference).GetMethod("InternalMakeTypedReference", BindingFlags.NonPublic | BindingFlags.Static);
    var sig = MethodSignature.FromMethodInfo(mi);
    var del = ReflectionTools.NewCustomDelegateType(sig.ReturnType, sig.ParameterTypes);
    var inv = mi.CreateDelegate(del);
    TypedReference tr;
    var ptr = Pointer.Box(&tr, typeof(void*));
    inv.DynamicInvoke(ptr, inst, new[]{fi.FieldHandle.Value}, fi.FieldType);
    __refvalue(tr, T) = val;
}

FromMethodInfoNewCustomDelegateType 来自 SharpUtils 并且需要调用 InternalMakeTypedReference,它能够获取对任何字段的引用。它的签名是这样的:

private unsafe static extern void InternalMakeTypedReference(void* result, object target, IntPtr[] flds, RuntimeType lastFieldType);

您可以用自己的方法替换库方法,它们只是为了构建适当的委托类型(Action 由于指针而不能使用)。由于内部 RuntimeType,您无法直接创建委托类型。令人惊讶的是,DynamicInvoke 在委托上工作,并创建了类型引用。

但是,要使用这个巫毒,我不得不稍微降低程序集中的安全检查,所以我不确定它是否也适用于您的程序集系统:

[assembly: System.Security.SecurityRules(System.Security.SecurityRuleSet.Level1, SkipVerificationInFullTrust=true)]

另外请注意,此代码使用了大量未记录的功能,并且可能随时停止工作。使用风险自负。

【讨论】:

    【解决方案2】:

    我有一些退出代码可能会对你有所帮助。

    /// <summary>
        /// create a getter delegate for a static field.
        /// </summary>
        /// <typeparam name="TField"></typeparam>
        /// <param name="staticType">the type that contains the field. </param>
        /// <param name="fieldName">the field that you want to get the value.</param>
        /// <returns>the getter delegate.</returns>
        public static Func<TField> CreateStaticFieldGetter<TField>(Type staticType, string fieldName)
        {
            var fieldInfo = staticType.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static);
            var body = Expression.Field(null, fieldInfo);
            Expression<Func<TField>> lambda = Expression.Lambda<Func<TField>>(body);
            return lambda.Compile();
        }
    
    
        public static Action<TField> CreateStaticFieldSetter<TField>(Type staticType, string fieldName)
        {
            ParameterExpression p1 = Expression.Parameter(typeof(TField), "p1");
            var fieldInfo = staticType.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static);
            var leftExpr = Expression.Field(null, fieldInfo);
            var body = Expression.Assign(leftExpr, p1);
            Expression<Action<TField>> lambda = Expression.Lambda<Action<TField>>(body, p1);
            return lambda.Compile();
        }
    
    
    
        public static Func<TTarget, TField> CreateInstanceFieldGetter<TTarget, TField>(string fieldName)
        {
            ParameterExpression p1 = Expression.Parameter(typeof(TTarget), "p1");
            var fieldInfo = typeof(TTarget).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
            var body = Expression.Field(Expression.Convert(p1, typeof(TTarget)), fieldInfo);
            Expression<Func<TTarget, TField>> lambda = Expression.Lambda<Func<TTarget, TField>>(body, p1);
            return lambda.Compile();
        }
    
    
    
        public static Action<TTarget, TProperty> CreateInstanceFieldSetter<TTarget, TProperty>(string filedName)
        {
            ParameterExpression p1 = Expression.Parameter(typeof(TTarget), "p1");
            ParameterExpression p2 = Expression.Parameter(typeof(TProperty), "p2");
    
            var member = typeof(TTarget).GetField(filedName, BindingFlags.NonPublic | BindingFlags.Instance);
            var m1 = Expression.MakeMemberAccess(Expression.Convert(p1, typeof(TTarget)), member);
            BinaryExpression body = Expression.Assign(m1, p2);
    
            Expression<Action<TTarget, TProperty>> lambda = Expression.Lambda<Action<TTarget, TProperty>>(body, p1, p2);
            return lambda.Compile();
        }
    

    【讨论】:

    • 我还建议删除非英语文档和空文档 cmets。
    • @Aimee Borada,对不起,当字段为只读时,代码无法工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-12
    • 1970-01-01
    相关资源
    最近更新 更多