2009-07-15 109 views

回答

30

這是一個相當危險的事情。

儘管確實可以像其他任何對象一樣序列化和反序列化委託,但委託是指向序列化它的程序內的方法的指針。如果你在另一個程序中反序列化對象,那麼你會得到一個SerializationException - 如果你幸運的話。

例如,讓我們修改Darin的計劃了一下:

class Program 
{ 
    [Serializable] 
    public class Foo 
    { 
     public Func<string> Del; 
    } 

    static void Main(string[] args) 
    { 
     Func<string> a = (() => "a"); 
     Func<string> b = (() => "b"); 

     Foo foo = new Foo(); 
     foo.Del = a; 

     WriteFoo(foo); 

     Foo bar = ReadFoo(); 
     Console.WriteLine(bar.Del()); 

     Console.ReadKey(); 
    } 

    public static void WriteFoo(Foo foo) 
    { 
     BinaryFormatter formatter = new BinaryFormatter(); 
     using (var stream = new FileStream("test.bin", FileMode.Create, FileAccess.Write, FileShare.None)) 
     { 
      formatter.Serialize(stream, foo); 
     } 
    } 

    public static Foo ReadFoo() 
    { 
     Foo foo; 
     BinaryFormatter formatter = new BinaryFormatter(); 
     using (var stream = new FileStream("test.bin", FileMode.Open, FileAccess.Read, FileShare.Read)) 
     { 
      foo = (Foo)formatter.Deserialize(stream); 
     } 

     return foo; 
    } 
} 

運行它,你會看到它創建的對象,將其序列,反序列化到一個新的對象,當你調用Del在新對象上返回「a」。優秀。好的,現在註釋掉WriteFoo的調用,以便程序只是反序列化對象。再次運行該程序,您會得到相同的結果。

現在交換a和b的聲明並運行該程序。讓人驚訝。現在反序列化的對象返回「b」。

發生這種情況是因爲實際上被序列化的是編譯器分配給lambda表達式的名稱。編譯器按照發現它們的順序將名稱分配給lambda表達式。

這就是這樣的風險:你沒有序列化委託,你在序列化一個符號。這是符號的,而不是符號代表的序列化。反序列化對象的行爲取決於該符號的值在反序列化它的程序中表示的內容。

在某種程度上,所有序列化都是如此。將對象反序列化爲一個與序列化程序不同的實現對象類的程序,樂趣就開始了。但序列化委託將序列化的對象耦合到序列化它的程序的符號表,而不是對象的類的實現。

如果是我,我會考慮明確這個耦合。我會創建一個Foo的靜態屬性,它是Dictionary<string, Func<string>>,用鍵和函數填充它,並在每個實例中存儲密鑰而不是函數。這使得反序列化程序負責填充字典,然後開始反序列化Foo對象。在某種程度上,這與使用BinaryFormatter來序列化委託所做的完全相同;不同之處在於這種方法使得反序列化程序將功能分配給符號的責任更加明顯。

15

實際上,您可以使用BinaryFormatter,因爲它保留了類型信息。而這裏的證明:

class Program 
{ 
    [Serializable] 
    public class Foo 
    { 
     public Func<string> Del; 
    } 

    static void Main(string[] args) 
    { 
     Foo foo = new Foo(); 
     foo.Del = Test; 
     BinaryFormatter formatter = new BinaryFormatter(); 
     using (var stream = new FileStream("test.bin", FileMode.Create, FileAccess.Write, FileShare.None)) 
     { 
      formatter.Serialize(stream, foo); 
     } 

     using (var stream = new FileStream("test.bin", FileMode.Open, FileAccess.Read, FileShare.Read)) 
     { 
      foo = (Foo)formatter.Deserialize(stream); 
      Console.WriteLine(foo.Del()); 
     } 
    } 

    public static string Test() 
    { 
     return "test"; 
    } 

} 

一個重要的事情,你應該知道,如果你決定使用BinaryFormatter的是,它的格式是沒有很好的記載和實施可能有.NET和/或CLR版本之間的重大更改的。

+1

你確定這個代理引用了一個非靜態方法嗎?我可以看到它使用靜態方法,因爲不需要定義Traget,但是例如方法它有什麼作用?潛在地,它可以序列化Target實例圖(假設它是可序列化的),但是隨後在反序列化和調用時它將位於具有潛在陳舊數據的不同實例上。我個人會非常小心地選擇以這種方式堅持代表,因爲它很容易導致一些意想不到且難以調試/修復的行爲。 – LBushkin 2009-07-15 17:53:44

+1

它也適用於非靜態方法。它序列化Target實例圖,並假設它是可序列化的(用SerializableAttribute標記)。 – 2009-07-15 18:05:26

2

委託是一種方法指針,當您說保存時,我可能會誤解,但如果嘗試保存並恢復地址,則在運行時添加到委託的位置可能不再存在。

+0

謝謝Quintin。 你是對的,作爲一個指針,我們不能。但是他們的內容呢?類似於C++ *操作符。 – 2009-07-15 17:22:48

1

所以,這是我的理解,你想「保存」一個函數指針(委託)。現在,如果將所有委託函數放入庫中,則可以使用系統反射在運行時構建鏈接,然後可以選擇將委託轉換爲編譯器定義的委託(該庫也將位於庫中)。唯一的缺點是目標方法必須是一個明確定義的位置,所以沒有匿名方法,因爲每次編譯時都會在編譯時定義位置。 下面是我能夠在運行時重新創建委託的代碼,需要您自擔風險並且沒有備註。

更新:您可以做的另一件事是創建一個自定義屬性,並將其應用於您想要創建到委託中的任何和所有方法。在運行時,使用系統反射,遍歷找到的導出類型,然後從具有自定義屬性的類型中選擇所有方法。這可能比你想要的要多,如果你還提供了一個「ID」值,那麼只有這樣才能使用,因此有一種通過主查找表將ID鏈接到所需委託的邏輯方式。

我也剛剛注意到由於風險因素你放棄了這種方法的評論,我將在這裏留下來提供另一種做事方式。

using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using System.Text; 
    using System.Runtime.Serialization; 
    using System.Reflection; 

    namespace RD.Runtime 
    { 
     [Serializable] 
     public struct RuntimeDelegate 
     { 
      private static class RuntimeDelegateUtility 
      { 
       public static BindingFlags GetSuggestedBindingsForMethod(MethodInfo method) 
       { 
        BindingFlags SuggestedBinding = BindingFlags.Default; 

        if (method.IsStatic) 
         SuggestedBinding |= BindingFlags.Static; 
        else 
         SuggestedBinding |= BindingFlags.Instance; 

        if (method.IsPublic) 
         SuggestedBinding |= BindingFlags.Public; 
        else 
         SuggestedBinding |= BindingFlags.NonPublic; 

        return SuggestedBinding; 
       } 

       public static Delegate Create(RuntimeDelegate link, Object linkObject) 
       { 
        AssemblyName ObjectAssemblyName = null; 
        AssemblyName DelegateAssemblyName = null; 
        Assembly ObjectAssembly = null; 
        Assembly DelegateAssembly = null; 
        Type ObjectType = null; 
        Type DelegateType = null; 
        MethodInfo TargetMethodInformation = null; 

        #region Get Assembly Names 
        ObjectAssemblyName = GetAssemblyName(link.ObjectSource); 
        DelegateAssemblyName = GetAssemblyName(link.DelegateSource); 
        #endregion 
        #region Load Assemblys 
        ObjectAssembly = LoadAssembly(ObjectAssemblyName); 
        DelegateAssembly = LoadAssembly(DelegateAssemblyName); 
        #endregion 
        #region Get Object Types 
        ObjectType = GetTypeFromAssembly(link.ObjectFullName, ObjectAssembly); 
        DelegateType = GetTypeFromAssembly(link.DelegateFullName, DelegateAssembly); 
        #endregion 
        #region Get Method 
        TargetMethodInformation = ObjectType.GetMethod(link.ObjectMethodName, link.SuggestedBinding); 
        #endregion 

        #region Create Delegate 
        return CreateDelegateFrom(linkObject, ObjectType, DelegateType, TargetMethodInformation); 
        #endregion 
       } 

       private static AssemblyName GetAssemblyName(string source) 
       { 
        return GetAssemblyName(source, source.ToUpper().EndsWith(".DLL") || source.ToUpper().EndsWith(".EXE")); 
       } 
       private static AssemblyName GetAssemblyName(string source, bool isFile) 
       { 
        AssemblyName asmName = null; 

        try 
        { 
         if (isFile) 
          asmName = GetAssemblyNameFromFile(source); 
         else 
          asmName = GetAssemblyNameFromQualifiedName(source); 
        } 
        catch (Exception err) 
        { 
         string ErrorFormatString = "Invalid Call to utility method 'GetAssemblyNameOrThrowException'\n" + 
                "Arguments passed in:\n" + 
                "=> Source:\n[{0}]\n" + 
                "=> isFile = {1}\n" + 
                "See inner exception(s) for more detail."; 
         throw new InvalidOperationException(string.Format(ErrorFormatString, source, isFile), err); 
        } 

        if (asmName == null) 
         throw new InvalidOperationException(asmName.Name + " Assembly Name object is null, but no other error was encountered!"); 

        return asmName; 
       } 
       private static AssemblyName GetAssemblyNameFromFile(string file) 
       { 
        #region Validate parameters 
        if (string.IsNullOrWhiteSpace(file)) 
         throw new ArgumentNullException("file", "given a null or empty string for a file name and path"); 
        if (!System.IO.File.Exists(file)) 
         throw new ArgumentException("File does not exsits", "file"); 
        #endregion 

        AssemblyName AssemblyNameFromFile = null; 

        try 
        { 
         AssemblyNameFromFile = AssemblyName.GetAssemblyName(file); 
        } 
        catch (Exception err) 
        { 
         throw err; 
        } 

        return AssemblyNameFromFile; 
       } 
       private static AssemblyName GetAssemblyNameFromQualifiedName(string qualifiedAssemblyName) 
       { 
        #region Validate parameters 
        if (string.IsNullOrWhiteSpace(qualifiedAssemblyName)) 
         throw new ArgumentNullException("qualifiedAssemblyName", "given a null or empty string for a qualified assembly name"); 
        #endregion 

        AssemblyName AssemblyNameFromQualifiedAssemblyName = null; 

        try 
        { 
         AssemblyNameFromQualifiedAssemblyName = new AssemblyName(qualifiedAssemblyName); 
        } 
        catch (Exception err) 
        { 
         throw err; 
        } 

        return AssemblyNameFromQualifiedAssemblyName; 
       } 

       private static Assembly LoadAssembly(AssemblyName assemblyName) 
       { 
        Assembly asm = LoadAssemblyIntoCurrentAppDomain(assemblyName); 
        if (asm == null) 
         throw new InvalidOperationException(assemblyName.Name + " Assembly is null after loading but no other error was encountered!"); 

        return asm; 
       } 
       private static Assembly LoadAssemblyIntoCurrentAppDomain(AssemblyName assemblyName) 
       { 
        #region Validation 
        if (assemblyName == null) 
         throw new ArgumentNullException("assemblyName", "Assembly name is null, must be valid Assembly Name Object"); 
        #endregion 

        return LoadAssemblyIntoAppDomain(assemblyName, AppDomain.CurrentDomain); 
       } 
       private static Assembly LoadAssemblyIntoAppDomain(AssemblyName assemblyName, AppDomain appDomain) 
       { 
        #region Validation 
        if (assemblyName == null) 
         throw new ArgumentNullException("assemblyName", "Assembly name is null, must be valid Assembly Name Object"); 
        if (appDomain == null) 
         throw new ArgumentNullException("appDomain", "Application Domain is null, must be a valid App Domain Object"); 
        #endregion 

        return appDomain.Load(assemblyName); 
       } 

       private static Type GetTypeFromAssembly(string targetType, Assembly inAssembly) 
       { 
        #region Validate 
        if (string.IsNullOrWhiteSpace(targetType)) 
         throw new ArgumentNullException("targetType", "Type name is null, empty, or whitespace, should be type's display name."); 
        if (inAssembly == null) 
         throw new ArgumentNullException("inAssembly", "Assembly is null, should be valid assembly"); 
        #endregion 

        try 
        { 
         return inAssembly.GetType(targetType, true); 
        } 
        catch (Exception err) 
        { 
         string ErrorFormatMessage = "Unable to retrive type[{0}] from assembly [{1}], see inner exception."; 
         throw new InvalidOperationException(string.Format(ErrorFormatMessage, targetType, inAssembly), err); 
        } 
       } 

       private static Delegate CreateDelegateFrom(Object linkObject, Type ObjectType, Type DelegateType, MethodInfo TargetMethodInformation) 
       { 
        if (TargetMethodInformation.IsStatic & linkObject == null) 
        { 
         return CreateStaticMethodDelegate(DelegateType, TargetMethodInformation); 
        } 

        if (linkObject != null) 
        { 
         ValidateLinkObjectType(linkObject, ObjectType); 
        } 
        else 
        { 
         linkObject = CreateInstanceOfType(ObjectType, null); 
        } 

        return CreateInstanceMethodDelegate(linkObject, DelegateType, TargetMethodInformation); 
       } 

       private static Delegate CreateStaticMethodDelegate(Type DelegateType, MethodInfo TargetMethodInformation) 
       { 
        return Delegate.CreateDelegate(DelegateType, TargetMethodInformation); 
       } 

       private static void ValidateLinkObjectType(object linkObject, Type ObjectType) 
       { 
        if (!ObjectType.IsInstanceOfType(linkObject)) 
        { 
         throw new ArgumentException(
          string.Format("linkObject({0}) is not of type {1}", linkObject.GetType().Name, ObjectType.Name), 
          "linkObject", 
          new InvalidCastException(
           string.Format("Unable to cast object type {0} to object type {1}", linkObject.GetType().AssemblyQualifiedName, ObjectType.AssemblyQualifiedName), 
           new NotSupportedException(
            "Conversions from one delegate object to another is not support with this version" 
           ) 
          ) 
         ); 
        } 
       } 

       private static Object CreateInstanceOfType(Type targetType, params Object[] parameters) 
       { 
        #region Validate 
        if (targetType == null) 
         throw new ArgumentNullException("targetType", "Target Type is null, must be valid System type."); 
        #endregion 

        try 
        { 
         return System.Activator.CreateInstance(targetType, parameters); 
        } 
        catch (Exception err) 
        { 
         string ErrorFormatMessage = "Invalid call to CreateInstanceOfType({0}, Object[])\n" + 
                "parameters found:\n" + 
                "{1}" + 
                "See inner exception for further information."; 
         string ParamaterInformationLine = GetParamaterLine(parameters); 

         throw new NotSupportedException(
          string.Format(ErrorFormatMessage, targetType.Name, ParamaterInformationLine), err); 
        } 

       } 
       private static string GetParamaterLine(Object[] parameters) 
       { 
        if (parameters == null) 
         return "NONE\n"; 

        string ParamaterFormatLine = "==> Paramater Type is {0} and object is {1}\n"; 
        string ParamaterInformationLine = string.Empty; 

        foreach (object item in parameters) 
        { 
         ParamaterInformationLine += string.Format(ParamaterFormatLine, item.GetType().Name, item); 
        } 

        return ParamaterInformationLine; 
       } 

       private static Delegate CreateInstanceMethodDelegate(Object linkObject, Type DelegateType, MethodInfo TargetMethodInformation) 
       { 
        return Delegate.CreateDelegate(DelegateType, linkObject, TargetMethodInformation); 
       } 
      } 

      public string ObjectSource; 
      public string ObjectFullName; 
      public string ObjectMethodName; 
      public string DelegateSource; 
      public string DelegateFullName; 
      public BindingFlags SuggestedBinding; 

      public RuntimeDelegate(Delegate target) 
       : this(target.Method.DeclaringType.Assembly.FullName, 
         target.Method.DeclaringType.FullName, 
         target.Method.Name, 
         target.GetType().Assembly.FullName, 
         target.GetType().FullName, 
         RuntimeDelegateUtility.GetSuggestedBindingsForMethod(target.Method)) { } 

      public RuntimeDelegate(
       string objectSource, 
       string objectFullName, 
       string objectMethodName, 
       string delegateSource, 
       string delegateFullName, 
       BindingFlags suggestedBinding) 
       :this() 
      { 
       #region Validate Arguments 
       if (string.IsNullOrWhiteSpace(objectSource)) 
        throw new ArgumentNullException("ObjectSource"); 
       if (string.IsNullOrWhiteSpace(objectFullName)) 
        throw new ArgumentNullException("ObjectFullName"); 
       if (string.IsNullOrWhiteSpace(objectMethodName)) 
        throw new ArgumentNullException("ObjectMethodName"); 
       if (string.IsNullOrWhiteSpace(delegateSource)) 
        throw new ArgumentNullException("DelegateSource"); 
       if (string.IsNullOrWhiteSpace(delegateFullName)) 
        throw new ArgumentNullException("DelegateFullName"); 
       #endregion 
       #region Copy values for properties 
       this.ObjectSource = objectSource; 
       this.ObjectFullName = objectFullName; 
       this.ObjectMethodName = objectMethodName; 
       this.DelegateSource = delegateSource; 
       this.DelegateFullName = delegateFullName; 
       this.SuggestedBinding = suggestedBinding; 
       #endregion 
      } 

      public Delegate ToDelegate() 
      { 
       return ToDelegate(null); 
      } 
      public Delegate ToDelegate(Object linkObject) 
      { 
       return RD.Runtime.RuntimeDelegate.RuntimeDelegateUtility.Create(this, linkObject); 
      } 
     } 
    } 
相關問題