2011-05-31 97 views
12

我有一個有趣的問題。我需要動態地包裝靜態類。即向我的呼叫者返回一個非靜態實例。例如:如何將靜態類包裝到非靜態實例對象中(動態)

public object CreateInstance(string className) { 
    Type t = assembly.GetType(className); 
    if (IsStatic(t)) { 
    return CreateStaticWrapper(t); 
    } else { 
    return Activator.CreateInstance(t); 
    } 
} 

所以我需要的是如何實施CreateStaticWrapper的指針。

注意:不幸的是我不能使用動態對象。

那麼我有什麼選擇?我並不熱衷於學習IL一代?如果IL生成(Reflection.Emit,或者現在有其他方法?)是否有路要走?

編輯:重要的是要注意我可以返回代表字典。所以我可以使用Delegate.CreateDelegate這個,但我似乎無法解決如何處理重載方法和泛型方法。

Edit2:另一種選擇是將空的構造函數注入到使用Emit的類型中,再次指向任何指針?這甚至可能在標記爲靜態的類型上?靜態關鍵字是否將其加入到IL中?

編輯3:對於一些上下文,我將它傳遞給javascript環境,請參閱:my project。所以我希望能夠(在JavaScript中):

var fileHelper = .create('System.IO.File'); 
if (fileHelper.Exists(fileName)) { fileHelper.Delete(fileName); } 

謝謝所有。

+0

您的目標是創建一個靜態類的內容的副本?是否有靜態類的非靜態等價物(與靜態類具有相同屬性的非靜態類?) – k3b 2011-05-31 08:30:10

+0

包裝物應該是什麼樣子?原始類中相應靜態成員的代理實例成員? – Einar 2011-05-31 08:52:33

+0

我已編輯( 3)有點上下文,基本上我把這個靜態類傳遞給一個javascript環境,所以是的代理需要有相同的簽名,我希望能夠做到(在JavaScript中):var fs = .create ('System.IO.File'); fs.Exists('filename'); – gatapia 2011-05-31 08:57:15

回答

1

我會說去IL一代。創建一個代理是一個非常簡單的場景。我實際上寫了一篇關於它的博客文章:einarwh.posterous.com/patching-polymorphic-pain-at-runtime。情況不同,但解決方案几乎完全相同。

除了不需要將「this」引用加載到堆棧上(因爲您正在執行靜態方法調用),您基本上可以完全按照在博客文章中所做的操作。

2

嘗試創建一個繼承自System.Dynamic.DynamicObject的包裝類。在包裝類中,使用反射來調用靜態類的方法。

你需要的東西是這樣的:

public class StaticWrapper<T> : System.Dynamic.DynamicObject 
{ 
    private static readonly Type t = typeof(T); 
    public static int MyProperty { get; set; } 
    public override bool TryInvokeMember(System.Dynamic.InvokeMemberBinder binder, object[] args, out object result) 
    { 
     try 
     { 
      result = t.InvokeMember(binder.Name, BindingFlags.Static | BindingFlags.Public, null, null, args); 
      return true; 
     } 
     catch 
     { 
      result = null; 
      return false; 
     } 
    } 
    public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result) 
    { 
     try 
     { 
      var p = t.GetProperty(binder.Name); 
      if (p != null) 
       result = p.GetValue(null, null); 
      else 
      { 
       var f = t.GetField(binder.Name); 
       if (f != null) result = f.GetValue(null); 
       else { result = null; return false; } 
      } 
      return true; 
     } 
     catch 
     { 
      result = null; 
      return false; 
     } 
    } 
    public override bool TrySetMember(System.Dynamic.SetMemberBinder binder, object value) 
    { 
     try 
     { 
      var p = t.GetProperty(binder.Name); 
      if (p != null) 
       p.SetValue(null, value, null); 
      else 
      { 
       var f = t.GetField(binder.Name); 
       if (f != null) f.SetValue(null, value); 
       else return false; 
      } 
      return true; 
     } 
     catch (SystemException) 
     { 
      return false; 
     } 
    } 
} 

希望工程。

+0

不幸的是整個家庭的動態對象是[不是我的選擇](http://javascriptdotnet.codeplex.com/discussions/235767)。這也是我的第一個直覺選擇,但是對於這個偉大的迴應來說是+1 – gatapia 2011-05-31 20:23:18

+0

在'InvokeMember'中添加'| BindingFlags.InvokeMethod'修復運行時錯誤。靜態類不能作爲泛型類型參數,例如安慰。 – IlPADlI 2015-01-10 13:28:26

1

所以,說我們玩「Delegate.CreateDelegate」的方式。讓我們看看我們是否能夠獲得有關後,你的其他問題的更多細節......讓我們先從:

public static object Generate(Type t) 
{ 
    if(IsStatic(t)) 
    { 
     var dictionary = new Dictionary<string, Delegate>(); 
     foreach (var methodInfo in t.GetMethods()) 
     { 
      var d = Delegate.CreateDelegate(t, methodInfo); 
      dictionary[methodInfo.Name] = d; 
     } 
     return dictionary; 
    } 
    return Activator.CreateInstance(t); 
} 

靜態類是「密封」,並因此不能被繼承。所以我沒有看到'超載'的含義。對於泛型方法,在將其添加到我們的字典之前,我們需要調用methodInfo.MakeGenericMethod(...)。但是,那麼你就需要事先知道是什麼類型,我猜你不......或者,你可以這樣做:

... 
if (methodInfo.IsGenericMethod) 
{ 
    d = new Func<MethodInfo, Type[], Delegate>(
     (method, types) => 
     Delegate.CreateDelegate(
      method.DeclaringType, method.MakeGenericMethod(types))); 
} 
dictionary[methodInfo.Name] = d; 
... 

這將使你的委託,將採取一個類型數組(該泛型類型參數),並從中產生一個工作委託。

+0

我真的很喜歡你處理泛型的方式,我會使用它,但是這並不能幫助我們重寫成員。即擁有System.IO.Directory.Delete(dirName)和Delete(dirName,遞歸)意味着我只能註冊一個「刪除」。我現在所做的只是註冊一個帶有一個args數組的Delete,然後動態委派給正確的實現。這具有在JavaScript中更改簽名的醜陋性。所以現在在JS中我需要這樣做:'Directory.Delete([dirname])'。所有其他呼叫都是真實的接口。 – gatapia 2011-05-31 20:27:27

1

好吧,我提出的解決方案如下,並發現閱讀和研究Einar's blog post,他發佈了上面的評論。謝謝Einar。

但我想我會後我的full code solution這裏的情況下,它可以幫助別人的未來:

using System; 
using System.Linq; 
using System.Reflection; 
using System.Reflection.Emit; 

namespace js.net.jish.Command 
{ 
    public class StaticTypeWrapper 
    { 
    private readonly Type staticType; 

    public StaticTypeWrapper(Type staticType) 
    { 
     this.staticType = staticType; 
    } 

    public object CreateWrapper() 
    { 
     string ns = staticType.Assembly.FullName;  
     ModuleBuilder moduleBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(ns), AssemblyBuilderAccess.Run).DefineDynamicModule(ns); 
     TypeBuilder wrapperBuilder = moduleBuilder.DefineType(staticType.FullName, TypeAttributes.Public, null, new Type[0]); 
     foreach (MethodInfo method in staticType.GetMethods().Where(mi => !mi.Name.Equals("GetType"))) 
     { 
     CreateProxyMethod(wrapperBuilder, method); 
     } 
     Type wrapperType = wrapperBuilder.CreateType(); 
     object instance = Activator.CreateInstance(wrapperType); 
     return instance; 
    } 

    private void CreateProxyMethod(TypeBuilder wrapperBuilder, MethodInfo method) 
    { 
     var parameters = method.GetParameters(); 

     var methodBuilder = wrapperBuilder.DefineMethod(method.Name, MethodAttributes.Public | MethodAttributes.Virtual, method.ReturnType, parameters.Select(p => p.ParameterType).ToArray()); 
     var gen = methodBuilder.GetILGenerator(); 

     for (int i = 1; i < parameters.Length + 1; i++) 
     { 
     gen.Emit(OpCodes.Ldarg, i); 
     } 
     gen.Emit(OpCodes.Call, method); 
     gen.Emit(OpCodes.Ret); 
    } 
    } 
}