簡體   English   中英

通過接口動態創建一個class

[英]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 swaggersolidity - > 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM