Создание DynamicMethod для установки поля только для чтения для любого типа
Моя цель - создать делегата во время выполнения, который может установить любое поле (включая readonly
) в любом типе ссылки на указанное пользователем значение. К сожалению, моя текущая реализация VerificationException
во время выполнения, когда сборки, содержащие типы, определяют [AllowPartiallyTrustedCallers]
приписывать.
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
поля никогда не должны устанавливаться после создания, но цель этого - десериализация и глубокое копирование.
2 ответа
Динамические методы весьма ограничены в изменении их безопасности. Я подозреваю, что использование 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;
}
FromMethodInfo и NewCustomDelegateType принадлежат SharpUtils и должны вызывать InternalMakeTypedReference, который может получить ссылку на любое поле. Его подпись выглядит так:
private unsafe static extern void InternalMakeTypedReference(void* result, object target, IntPtr[] flds, RuntimeType lastFieldType);
Вы можете заменить библиотечные методы своими собственными, они нужны только для создания соответствующего типа делегата (действие не может быть использовано из-за указателя). Вы не можете создать тип делегата напрямую из-за внутреннего RuntimeType. Удивительно, но DynamicInvoke работает с делегатом, и создается ссылка на тип.
Однако, чтобы использовать это вуду, мне пришлось немного снизить проверки безопасности в сборке, поэтому я не уверен, работает ли он и в вашей сборочной системе:
[assembly: System.Security.SecurityRules(System.Security.SecurityRuleSet.Level1, SkipVerificationInFullTrust=true)]
Также обратите внимание, что этот код использует множество недокументированных функций и может перестать работать в любое время. Используйте только на свой страх и риск.
У меня есть некоторые коды выхода, которые могут вам помочь.
/// <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();
}