[英]Dynamically create a class by interface
當我能夠動態生成方法時,我對.Net Expressions
有一些經驗。 沒關系,很好。
但現在我需要生成一個完整的 class,似乎唯一的方法是發射整個 IL,這是完全不可接受的(不可能支持)。
假設我們有以下接口:
public interface IFoo
{
[Description("5")]
int Bar();
[Description("true")]
bool Baz();
}
應轉換為:
public class Foo : IFoo
{
public int Bar() => 5;
public bool Baz() => true;
}
我怎樣才能實現它? 如果沒有 3rd 方工具和庫,它甚至可能嗎? 我知道 GitHub 上有很多有用的實用程序,但我真的不想導入整個 MVVM 框架來生成一些代碼。
如果我可以只使用Expressions
,並使用我已經用它生成的方法創建一個 class 。 但現在我不知道該怎么做。
首先,由於你正在處理遠程處理,我不得不提到這是.NET最初設計的基礎上支持(從.NET的根源作為COM 2.0)。 您最直接的解決方案是實現透明的遠程代理 - 只需創建自己的(可能是通用的)類派生自System.Runtime.Remoting.Proxies.RealProxy
,並且您可以通過覆蓋提供實現所需功能所需的所有邏輯Invoke
方法。 使用GetTransparentProxy
,您可以獲得實現您的界面的代理,並且您很高興。
顯然,在每次調用期間,這都會在運行時產生成本。 但是,它通常完全不重要,因為你正在進行任何I / O,特別是如果你正在處理網絡。 實際上,除非你處於緊密的循環中,否則即使不進行I / O也是非常不重要的 - 只有性能測試才能真正判斷出你是否對成本有好處。
如果您真的想要預生成所有方法體,而不是在運行時保持邏輯動態,則可以利用LambdaExpression
為您提供CompileToMethod
的事實。 與Compile
不同,您沒有得到一個可以直接調用的好的小委托,但是它為您提供了使用lambda表達式顯式構建方法體的選項 - 這反過來又允許您在不訴諸委托調用的情況下創建整個類。
一個完整(但簡單)的例子:
void Main()
{
var ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("TestAssembly"), AssemblyBuilderAccess.Run);
var mb = ab.DefineDynamicModule("Test");
var tb = mb.DefineType("Foo");
tb.AddInterfaceImplementation(typeof(IFoo));
foreach (var imethod in typeof(IFoo).GetMethods())
{
var valueString = ((DescriptionAttribute)imethod.GetCustomAttribute(typeof(DescriptionAttribute))).Description;
var method =
tb.DefineMethod
(
"@@" + imethod.Name,
MethodAttributes.Private | MethodAttributes.Static,
imethod.ReturnType,
new [] { tb }
);
// Needless to say, I'm making a lot of assumptions here :)
var thisParameter = Expression.Parameter(typeof(IFoo), "this");
var bodyExpression =
Expression.Lambda
(
Expression.Constant
(
Convert.ChangeType(valueString, imethod.ReturnType)
),
thisParameter
);
bodyExpression.CompileToMethod(method);
var stub =
tb.DefineMethod(imethod.Name, MethodAttributes.Public | MethodAttributes.Virtual, imethod.ReturnType, new Type[0]);
var il = stub.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Call, method, null);
il.Emit(OpCodes.Ret);
tb.DefineMethodOverride(stub, imethod);
}
var fooType = tb.CreateType();
var ifoo = (IFoo)Activator.CreateInstance(fooType);
Console.WriteLine(ifoo.Bar()); // 5
Console.WriteLine(ifoo.Baz()); // True
}
public interface IFoo
{
[Description("5")]
int Bar();
[Description("true")]
bool Baz();
}
如果你曾經使用.NET發射,這應該是非常簡單的。 我們定義了動態程序集,模塊,類型(理想情況下,您希望在單個動態程序集中一次定義所有類型)。 棘手的部分是Lambda.CompileToMethod
只支持靜態方法,所以我們需要作弊。 首先,我們創建一個靜態方法,將this
作為參數並在那里編譯lamdba表達式。 然后,我們創建一個方法存根 - 一個簡單的IL,確保我們的靜態方法被正確調用。 最后,我們將接口方法綁定到存根。
在我的示例中,我假設一個無參數方法,但只要您確保LambdaExpression
使用與接口方法完全相同的類型,存根就像在序列中執行所有Ldarg
一樣簡單,只需一個Call
還有一個Ret
。 如果你的真實代碼(在靜態方法中)足夠短,它通常會被內聯。 因為this
是一個像其他任何一個的論點,如果你有冒險精神,你可以采取生成的方法的方法體,並將其直接放入虛擬方法 - 請注意你需要在兩個通道中這樣做但是。
您可以使用CodeDOM和Emit。 但是,我不認為這是值得的。
對於類似的事情,我使用以下庫: http : //www.castleproject.org/projects/dynamicproxy/
即使目標類不可用,也能夠創建代理類(然后必須攔截所有方法)。
我實際上目前正在使用CodeGeneration.Roslyn
包,它允許您集成到MSBuild管道並構建東西。 例如,請參閱我的REST swagger或solidity - > C#codegen 。
如果您只想要一個動態支持者,您可以使用 DynamicClassFactory
void Main()
{
var properties = typeof(IFoo).GetProperties()
.Select(x => new DynamicProperty(x.Name, x.PropertyType))
.ToList();
Type type = DynamicClassFactory.CreateType(properties);
dynamic dynamicFooClass = Activator.CreateInstance(type);
dynamicFooClass.Key = 1;
dynamicFooClass.Description = "Freddy";
Console.WriteLine(dynamicFooClass);
}
public interface IFoo
{
int Key { get; set; }
string Description { get; set; }
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.