Mit Roslyn können Sie dynamisch eine Klasse erstellen, die von einem Interface (oder einer abstrakten Klasse) erbt.
Ich benutze das Folgende, um konkrete Klassen aus abstrakten Klassen zu erstellen.
In diesem Beispiel ist AAnimal eine abstrakte Klasse.
var personClass = typeof(AAnimal).CreateSubclass("Person");
Dann können Sie einige Objekte instanziieren:
var person1 = Activator.CreateInstance(personClass);
var person2 = Activator.CreateInstance(personClass);
Ohne Zweifel funktioniert dies nicht für jeden Fall, aber es sollte ausreichen, um Sie zum Laufen zu bringen:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace Publisher
{
public static class Extensions
{
public static Type CreateSubclass(this Type baseType, string newClassName, string newNamespace = "Magic")
{
//todo: handle ref, out etc.
var concreteMethods = baseType
.GetMethods()
.Where(method => method.IsAbstract)
.Select(method =>
{
var parameters = method
.GetParameters()
.Select(param => $"{param.ParameterType.FullName} {param.Name}")
.ToString(", ");
var returnTypeStr = method.ReturnParameter.ParameterType.Name;
if (returnTypeStr.Equals("Void")) returnTypeStr = "void";
var methodString = @$"
public override {returnTypeStr} {method.Name}({parameters})
{{
Console.WriteLine(""{newNamespace}.{newClassName}.{method.Name}() wurde aufgerufen"");
}}";
return methodString.Trim();
})
.ToList();
var concreteMethodsString = concreteMethods
.ToString(Environment.NewLine + Environment.NewLine);
var classCode = @$"
using System;
namespace {newNamespace}
{{
public class {newClassName}: {baseType.FullName}
{{
public {newClassName}()
{{
}}
{concreteMethodsString}
}}
}}
".Trim();
classCode = FormatUsingRoslyn(classCode);
/*
var assemblies = new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(baseType.Assembly.Location),
};
*/
var assemblies = AppDomain
.CurrentDomain
.GetAssemblies()
.Where(a => !string.IsNullOrEmpty(a.Location))
.Select(a => MetadataReference.CreateFromFile(a.Location))
.ToArray();
var syntaxTree = CSharpSyntaxTree.ParseText(classCode);
var compilation = CSharpCompilation
.Create(newNamespace)
.AddSyntaxTrees(syntaxTree)
.AddReferences(assemblies)
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
var result = compilation.Emit(ms);
//compilation.Emit($"C:\\Temp\\{newNamespace}.dll");
if (result.Success)
{
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load(ms.ToArray());
var newTypeFullName = $"{newNamespace}.{newClassName}";
var type = assembly.GetType(newTypeFullName);
return type;
}
else
{
IEnumerable failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
{
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
return null;
}
}
}
public static string ToString(this IEnumerable list, string separator)
{
string result = string.Join(separator, list);
return result;
}
public static string FormatUsingRoslyn(string csCode)
{
var tree = CSharpSyntaxTree.ParseText(csCode);
var root = tree.GetRoot().NormalizeWhitespace();
var result = root.ToFullString();
return result;
}
}
}
1 Stimmen
Link scheint veraltet zu sein, dies könnte eine geeignete Alternative sein liensberger.it/web/blog/?p=298.
2 Stimmen
Ja, Sie können dies mit .NET 4 und höher (über das DLR) mithilfe des ImpromptuInterface NuGet-Pakets tun.
1 Stimmen
@PhilCooper Dein Link war wahrscheinlich seit mindestens 2016 nicht verfügbar, aber zum Glück wurde er vorher archiviert. web.archive.org/web/20111105150920/http://www.liensberger.it/…
0 Stimmen
Im C#9 können Sie records verwenden (devblogs.microsoft.com/dotnet/welcome-to-c-9-0). Ich weiß, dass dies nicht dasselbe ist, aber auch ein sehr leichtgewichtiger Ansatz, "einfache" Schnittstellen zu implementieren :)