2009-07-15 79 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); 
      } 
     } 
    } 
相关问题