В настоящее время я пытаюсь создать «макет» для интерфейса с использованием Reflection.Emit. Поэтому я создал базовый класс, который использую для всех динамически генерируемых моков. Для свойств в интерфейсе я хочу вызвать метод «Получить» в базовом классе, который возвращает значение свойства.
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);
}
}
}
К сожалению, я всегда получаю 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;
var setter = typeBuilder.DefineMethod("set_" + prop.Name, MethodAttributes.Public, typeof(void), Type.EmptyTypes);
определяет метод, который не принимает аргументов (поскольку вы не указали типы для аргументов). Это неправильно. Кроме того, вы можете показать полный рабочий код? Включая код, который реализует ваш интерфейс, чтобы мы могли его воспроизвести? - person Rob   schedule 20.07.2017