Das ist nur ein Versuch.
Neulich kam mir die interessante Idee, ob es wohl ohne Aspekt-Orientierte-Programmierung, einfach nur mit den Standardmitteln von C# wohl möglich ist, zur Laufzeit ein Interface in eine Klasse zu implementieren.
Dabei ist folgender Code entstanden, der IMHO sehr gut funktioniert.
Habe mich dazu auch ein einigen Beispieln im Internet orientiert, die sich mit den Klassen System.Reflection und System.Reflection.Emit befassen.
using System;
using System.Data;
using System.Windows.Forms;
using System.Threading;
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics;
using System.Collections.Generic;
namespace Library.ExtendedBaseTypes.Generics
{
/// <summary>
/// This is the base class for the automatically generated wrappers.
/// </summary>
public class WrapperBase
{
internal protected object m_oSource = null;
internal object NewFromPrototype(object _source)
{
WrapperBase newWrapper = (WrapperBase)MemberwiseClone();
newWrapper.m_oSource = _source;
return newWrapper;
}
}
/// <summary>
/// This internal class generates a wrapper type from one type to an interface
/// the type does not implement.
/// </summary>
internal class WrapperGenerator
{
static AppDomain m_AppDomain;
static AssemblyBuilder m_AssemblyBuilder;
static ModuleBuilder m_ProxyModule;
static WrapperGenerator()
{
m_AppDomain = Thread.GetDomain();
m_AssemblyBuilder = m_AppDomain.DefineDynamicAssembly(new AssemblyName("Wrappers"),AssemblyBuilderAccess.Run);
m_ProxyModule = m_AssemblyBuilder.DefineDynamicModule("WrapperModule", false);
}
/// <summary>
/// Generates a wrapper type using System.Reflection.Emit.
/// </summary>
/// <param name="tgt">The target type. Must be an interface</param>
/// <param name="src">The source type. Must contain all methods required for implementing the target type, but must not actually implement the target type.</param>
/// <returns>The wrapper type</returns>
/// <exception cref="System.MissingMethodException">if <paramref name="src"/> does not contain all required methods to implement <paramref name="dst"/></exception>
public static Type GenerateWrapperType(Type _targetType, Type _source)
{
Trace.Assert(_targetType.IsInterface);
TypeBuilder proxyBuilder = m_ProxyModule.DefineType(
_source.Name + "To" + _targetType.Name + "Wrapper",
TypeAttributes.NotPublic | TypeAttributes.Sealed,
typeof(WrapperBase),
new Type[] { _targetType });
FieldInfo srcField = typeof(WrapperBase).GetField("m_oSource", BindingFlags.Instance | BindingFlags.NonPublic);
foreach (MethodInfo method in _targetType.GetMethods())
{
ParameterInfo[] aParameter = method.GetParameters();
Type[] aParameterType = new Type[aParameter.Length];
for (int i = 0; i < aParameter.Length; i++)
{
aParameterType[i] = aParameter[i].ParameterType;
}
MethodBuilder methodBuilder = proxyBuilder.DefineMethod(method.Name,
MethodAttributes.Public | MethodAttributes.Virtual,
method.ReturnType, aParameterType);
ILGenerator ilGenerator = methodBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0); //load this
ilGenerator.Emit(OpCodes.Ldfld, srcField); //load this->src
//push parameters on the stack
for (int i = 1; i < aParameter.Length + 1; i++)
{
ilGenerator.Emit(OpCodes.Ldarg, i);
}
MethodInfo sourceMethod = _source.GetMethod(method.Name, aParameterType);
if (sourceMethod == null)
throw new MissingMethodException("The source type " + _source.FullName + " does not contain the " + method.Name + " method " +
"required to implement the " + _targetType.FullName + " interface!");
//call (virtual if nessecary)
if (method.IsVirtual)
{
ilGenerator.Emit(OpCodes.Callvirt, sourceMethod);
}
else
{
ilGenerator.Emit(OpCodes.Call, sourceMethod);
}
ilGenerator.Emit(OpCodes.Ret); //return
}
return proxyBuilder.CreateType();
}
public static WrapperBase GenerateWrapperPrototype(Type _targetType, Type _sourceType)
{
// emit a wrapper type to wrap T as implementing I.
// This is where an exception might be thrown.
Type wrapperType = GenerateWrapperType(_targetType, _sourceType);
// create a prototype object because Activator.CreateInstance
// is too slow to use it every time.
return (WrapperBase)Activator.CreateInstance(wrapperType);
}
}
internal sealed class Latent<I, T>
{
static WrapperBase wrapperPrototype;
static Latent()
{
wrapperPrototype = WrapperGenerator.GenerateWrapperPrototype(typeof(I), typeof(T));
}
public static I Cast(T src)
{
return (I)wrapperPrototype.NewFromPrototype(src);
}
}
/// <summary>
/// A class that automatically generates wrapper classes for types that
/// implement all required methods for an interface, but do not explicitly
/// implement the interface. For example if you have an object <code>a</code>
/// of a type that contains a <code>int CompareTo(object o);</code> method but
/// does not implement the <code>IComparable</code> interface, you could
/// automatically create a wrapper using
/// <code></code>
/// or
/// <code></code>
/// </summary>
/// <typeparam name="I">The interface to create wrappers for.</typeparam>
public sealed class Latent<I>
{
private static Dictionary<Type, WrapperBase> wrapperPrototypes = null;
private static WrapperBase GetWrapperPrototype(Type type)
{
if (wrapperPrototypes == null)
wrapperPrototypes = new Dictionary<Type, WrapperBase>();
if (wrapperPrototypes.ContainsKey(type))
return wrapperPrototypes[type];
else
return wrapperPrototypes[type] = WrapperGenerator.GenerateWrapperPrototype(typeof(I), type);
}
/// <summary>
/// The dynamic cast method. Generates a wrapper if necessary, so the first invocation
/// for a new source type will take more time than subsequent invocations.
/// </summary>
/// <param name="src">The object to wrap.</param>
/// <returns>The wrapper, conveniently casted to <typeparamref name="I"/>.</returns>
/// <exception cref="System.MissingMethodException">if <paramref name="src"/> does not contain all required methods to implement <typeparamref name="I"/></exception>
public static I CastObject(object _oSource)
{
if (typeof(I).IsAssignableFrom(_oSource.GetType()))
return (I)_oSource;
else
return (I)GetWrapperPrototype(_oSource.GetType()).NewFromPrototype(_oSource);
}
/// <summary>
/// The static cast method. Generates a wrapper if nessecary, so the first invocation
/// for a new source type will take more time than subsequent invocations.
/// </summary>
/// <param name="src">The object to wrap.</param>
/// <typeparam name="T">The type of the object to wrap</typeparam>
/// <returns>The wrapper, conveniently casted to <typeparamref name="I"/>.</returns>
/// <exception cref="System.MissingMethodException">if <paramref name="src"/> does not contain all required methods to implement <typeparamref name="I"/></exception>
public static I Cast<T>(T src)
{
return Latent<I, T>.Cast(src);
}
}
Jetzt könnten wir zum Beispiel folgendes schreiben:
/// <summary>
/// This is a test interface
/// </summary>
public interface ITest
{
void DoIt();
void Print(string a, string b);
void Print(int x, int y);
int Sum(int x, int y);
}
/// <summary>
/// This is a class that implements all required methods for the above
/// interface ITest, but does not explicitly implement the ITest interface.
/// </summary>
public class Test
{
//no parameters at all
public void DoIt()
{
System.Console.WriteLine("DoIt()");
}
//normal reference types
public void Print(string a, string b)
{
System.Console.WriteLine("Print({0},{1})", a, b);
}
//primitive types
public void Print(int i, int j)
{
System.Console.WriteLine("Print({0},{1})", i, j);
}
//return primitive types
public int Sum(int i, int j)
{
return i + j;
}
}
}
class TestMain
{
public static void Main()
{
try
{
Test test = new Test();
ITest itest = Latent<ITest>.Cast(test); //Diese Zeile hier ist wichtig
itest.DoIt();
itest.Print("Hello", "World");
itest.Print(1, 2);
Console.WriteLine(itest.Sum(1, 2));
}
catch (Exception e)
{
Console.WriteLine(e);
Console.WriteLine(e.StackTrace);
}
}
}
In der markierten Zeile wird jetzt der obige Mechanismus angeworfen, die Dynamische Cast Methode generiert automatisch einen Wrapper falls benötigt.
Alles was dafür benötigt wird, wird automatisch mitkopiert.
Und wir erhalten am Ende ein Interface das automatisch richtig implementiert wird.
Jetzt können wir ganz normal die Funktionalität von "Test" über das Interface "ITest" aufrufen.
Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.
Siehe auch (als Ergänzung):
Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.
System.Reflection.DispatchProxy 😉
Hab ich selber nie genutzt, klingt aber vergleichbar.
NuGet Packages im Code auslesen
lock Alternative für async/await
Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.
@Palladin. Ja, das gabs damals noch nicht. 😁 und selbst wenn, dann hätten womöglich die meisten das noch nicht gekannt.
Witzigerweise habe ich genau heute den _DispatchProxy _das erste mal ausprobiert. 😁
Bisher finde ich da meine Klasse noch handlicher, ABER wenn das schon .NET jetzt im Gepäck hat und es funktioniert, way to go. 🙂
Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.