我正在尝试使用Reflection.Emit为界面创建“模拟”。 因此,我创建了一个基类,用于所有动态生成的模拟。 对于接口中的属性,我想在基类中调用一个返回属性值的“Get”方法。
public class Mock
{
public static TIf Wrap<TIf>() where TIf : class
{
if (!typeof(TIf).IsInterface)
throw new Exception(typeof(TIf) + " is no interface");
var asmBuilder = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.Run);
var modBuilder = asmBuilder.DefineDynamicModule("Mock", true);
var typename = "ImplOf" + typeof(TIf).Name.Replace(" ", "") + ".Mock";
var typeBuilder = modBuilder.DefineType(typename, TypeAttributes.Public, typeof(WrapperBase));
typeBuilder.AddInterfaceImplementation(typeof(TIf));
// methods
foreach (var meth in typeof(TIf).GetMethods())
{
var del = typeof(WrapperBase).GetMethod(meth.ReturnType != typeof(void) ? "TryCallMethod" : "TryCallMethodOneWay");
var mb = typeBuilder.DefineMethod(meth.Name, meth.Attributes ^ MethodAttributes.Abstract);
mb.SetParameters(meth.GetParameters().Select(p => p.ParameterType)?.ToArray());
mb.SetReturnType(meth.ReturnType);
var mbil = mb.GetILGenerator();
mbil.Emit(OpCodes.Ldarg_0);
mbil.Emit(OpCodes.Ldstr, meth.Name);
for (var i = 0; i < meth.GetParameters().Length; i++)
{
mbil.Emit(OpCodes.Ldarg, i + 1);
}
mbil.Emit(OpCodes.Call, del);
mbil.Emit(OpCodes.Ret);
}
// properties
foreach (var prop in typeof(TIf).GetProperties())
{
var propertyBuilder = typeBuilder.DefineProperty(prop.Name, prop.Attributes, prop.PropertyType, null);
if (prop.CanRead)
{
var getterDelegate = typeof(WrapperBase).GetMethod("TryGetProperty");
var getter = typeBuilder.DefineMethod("get_" + prop.Name, MethodAttributes.Public, prop.PropertyType, Type.EmptyTypes);
var gil = getter.GetILGenerator();
gil.Emit(OpCodes.Ldarg_0);
gil.Emit(OpCodes.Ldstr, prop.Name);
gil.Emit(OpCodes.Callvirt, getterDelegate);
gil.Emit(OpCodes.Castclass, prop.PropertyType);
gil.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getter);
}
if (prop.CanWrite)
{
var setterDelegate = typeof(WrapperBase).GetMethod("TrySetProperty");
var setter = typeBuilder.DefineMethod("set_" + prop.Name, MethodAttributes.Public, typeof(void), Type.EmptyTypes);
var sil = setter.GetILGenerator();
sil.Emit(OpCodes.Ldarg_0);
sil.Emit(OpCodes.Ldstr, prop.Name);
sil.Emit(OpCodes.Ldarg_1);
sil.Emit(OpCodes.Call, setterDelegate);
sil.Emit(OpCodes.Ret);
propertyBuilder.SetSetMethod(setter);
}
}
var retType = typeBuilder.CreateType();
return retType.GetConstructor(new Type[0]).Invoke(new object[0]) as TIf;
}
public abstract class WrapperBase
{
public event Func<string, object[], object> OnTryCallMethod;
public event Action<string, object[]> OnTryCallMethodOneWay;
public event Func<string, object> OnTryGetProperty;
public event Action<string, object> OnTrySetProperty;
/// <inheritdoc />
public object TryCallMethod(string name, object[] pars)
{
return OnTryCallMethod?.Invoke(name, pars);
}
/// <inheritdoc />
public void TryCallMethodOneWay(string name, object[] pars)
{
OnTryCallMethodOneWay?.Invoke(name, pars);
}
/// <inheritdoc />
public object TryGetProperty(string name)
{
return OnTryGetProperty?.Invoke(name);
}
/// <inheritdoc />
public void TrySetProperty(string name, object value)
{
OnTrySetProperty?.Invoke(name, value);
}
}
}
不幸的是,在尝试读取“mocked”属性时,我总是会遇到InvalidProgramException。 设置属性(也将调用委托给某些基类方法)工作正常,方法调用也一样。
为了测试,我创建了一个非常简单的界面:
public interface ITest
{
void Show(string text);
string Text { get; set; }
}
现在我正在调用这样的模拟:
var wrapped = Mock.Wrap<ITest>();
// ***************** works - EventHandler is called with correct parameters!
((Mock.WrapperBase)wrapped).OnTryCallMethodOneWay += (s, objects) => { };
wrapped.Show("sss");
// ***************** works - EventHandler is called with correct parameters!
wrapped.Text = "";
((Mock.WrapperBase)wrapped).OnTrySetProperty += (s, val) => { };
// ***************** does NOT work - getting InvalidProgramException
((Mock.WrapperBase)wrapped).OnTryGetProperty += s => "";
var t = wrapped.Text;
答案 0 :(得分:1)
经过一些调试后,我发现了你的问题。我注意到了
当wrapped.Text = ""
明确写成TryCallMethodOneWay
时, TrySetProperty
正在进入foreach (var meth in typeof(TIf).GetMethods())
。
这是因为var properties = typeof(TIf).GetProperties();
var propertyMethods = properties.SelectMany(p => new[] { p.GetGetMethod(), p.GetSetMethod() }).ToLookup(p => p);
foreach (var meth in typeof(TIf).GetMethods())
{
if (propertyMethods.Contains(meth))
continue;
...
}
将返回getter和setter方法。那是;你要两次定义getter和setter。
通过简单的检查解决了这个问题:
Virtual
现在,如果他们要实现接口,您还必须将实施方法标记为var getter = typeBuilder.DefineMethod("get_" + prop.Name, MethodAttributes.Public | MethodAttributes.Virtual, prop.PropertyType, Type.EmptyTypes);
。因此,您需要将代码更改为以下内容:
var setter = typeBuilder.DefineMethod("set_" + prop.Name, MethodAttributes.Public | MethodAttributes.Virtual, typeof(void), new[] { prop.PropertyType });
和
{{1}}
您的代码应该可以正常运行