2009-07-15 4 views

Antwort

30

Dies ist eine ziemlich riskante Sache zu tun.

Während es wahr ist, dass Sie einen Delegaten wie jedes andere Objekt serialisieren und deserialisieren können, ist der Delegat ein Zeiger auf eine Methode innerhalb des Programms, die ihn serialisiert. Wenn Sie das Objekt in einem anderen Programm deserialisieren, erhalten Sie eine SerializationException - wenn Sie Glück haben.

Zum Beispiel lassen Sie uns Darins Programm ein wenig ändern:

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; 
    } 
} 

Run, und Sie werden sehen, dass es das Objekt erstellt, serialisiert es, deserialisiert es in ein neues Objekt, und wenn Sie Del nennen auf dem neuen Objekt gibt es "a" zurück. Ausgezeichnet. Okay, jetzt kommentieren Sie den Aufruf an WriteFoo, so dass das Programm es gerade deserialisiert das Objekt. Führen Sie das Programm erneut aus und Sie erhalten das gleiche Ergebnis.

Vertauschen Sie jetzt die Deklaration von a und b und führen Sie das Programm aus. Huch. Jetzt gibt das deserialisierte Objekt "b" zurück.

Dies passiert, weil das, was tatsächlich serialisiert wird, der Name ist, den der Compiler dem Lambda-Ausdruck zuweist. Und der Compiler ordnet Lambda-Ausdrücken in der Reihenfolge, in der er sie findet, Namen zu.

Und das ist riskant: Sie serialisieren den Delegaten nicht, Sie serialisieren ein Symbol. Es ist der Wert des Symbols, und nicht was das Symbol darstellt, wird serialisiert. Das Verhalten des deserialisierten Objekts hängt davon ab, was der Wert dieses Symbols in dem Programm darstellt, das es deserialisiert.

In gewissem Maße gilt dies für alle Serialisierung. Deserialisieren Sie ein Objekt in ein Programm, das die Klasse des Objekts anders als das Serialisierungsprogramm implementiert, und der Spaß beginnt. Aber das Serialisieren von Delegaten koppelt das serialisierte Objekt mit der Symboltabelle des Programms, das es serialisierte, und nicht mit der Implementierung der Klasse des Objekts.

Wenn ich es wäre, würde ich diese Kopplung explizit machen. Ich würde eine statische Eigenschaft von Foo erstellen, die eine Dictionary<string, Func<string>> war, füllen Sie diese mit Schlüsseln und Funktionen, und speichern Sie den Schlüssel in jeder Instanz und nicht die Funktion. Dies macht das Deserialisierungsprogramm für das Auffüllen des Wörterbuchs verantwortlich, bevor es mit der Deserialisierung von Foo-Objekten beginnt. In gewissem Maße ist dies genau dasselbe, was die Verwendung der BinaryFormatter zur Serialisierung eines Delegaten tut; Der Unterschied besteht darin, dass dieser Ansatz die Verantwortung des Deserialisierungsprogramms für die Zuweisung von Funktionen zu den Symbolen deutlich erhöht.

+2

Ich entschied mich schließlich, keine Delegierten in Dateien zu speichern Speichern von Delegaten in Dateien führt zu einem anderen Problem: Mehrere Kopien einer gleichen Funktion in einer Datei gespeichert werden. Eher (wie Robert sagt) ist es besser, ein Array von Delegaten zu definieren und den Index jedes Delegaten in der Datei zu speichern. –

+0

+1 diese Antwort ersparte mir eine Menge Schmerzen bei der Behandlung eines solchen Fehlers. Ein anderer Grund, den der Stellvertreter ändern kann, ist, wenn eine andere Compiler-Version verwendet wird: http://StackOverflow.com/a/40780504/66372 – eglasius

15

Eigentlich können Sie mit BinaryFormatter, wie es Typinformationen erhält. Und hier ist der Beweis:

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"; 
    } 

} 

Eine wichtige Sache, die Sie beachten sollten, wenn Sie sich entscheiden, zu verwenden BinaryFormatter ist, dass das Format nicht gut dokumentiert ist und die Umsetzung zu brechen Änderungen zwischen .NET und/oder CLR-Versionen haben könnte.

+1

Sind Sie sicher, dass dies funktioniert, wenn der Delegat auf eine nicht statische Methode verweist? Ich kann es mit statischen Methoden arbeiten sehen, da kein Traget definiert werden muss, aber zum Beispiel Methoden, was macht es? Möglicherweise könnte das Zielinstanzdiagramm serialisiert werden (vorausgesetzt, es ist serialisierbar). Wenn es jedoch deserialisiert und aufgerufen wird, befindet es sich in einer anderen Instanz mit potenziell veralteten Daten. Ich persönlich würde sehr vorsichtig sein, wenn es darum geht, Delegierte auf diese Weise zu beharren, da dies leicht zu unerwarteten und schwer zu behebenden Fehlern führen könnte. – LBushkin

+1

Es funktioniert auch mit nicht-statischen Methoden. Es serialisiert das Zielinstanzdiagramm auch unter der Annahme, dass es serialisierbar ist (markiert mit SerializableAttribute). –

2

Ein Delegat ist ein Methodenzeiger, den ich falsch verstehen kann, wenn Sie "save" sagen, aber der zum Delegat zur Laufzeit hinzugefügte Speicherort möglicherweise nicht mehr vorhanden ist, wenn Sie versuchen, die Adresse zu speichern und wiederherzustellen.

+0

Danke Quintin. Sie haben Recht, als Zeiger können wir nicht. Aber was ist mit ihren Inhalten? alles wie C++ * Operator. –

+4

Wenn Sie einen binären Serializer verwenden, wird der Delegat serialisiert, ebenso wie das gesamte Objektdiagramm, auf das es sich bezieht. Dies garantiert, dass der Delegat nach der Deserialisierung aufgerufen werden kann. –

1

So, es ist mein Verständnis, dass Sie einen Funktionszeiger (Delegat) "speichern" möchten. Wenn Sie jetzt alle Ihre Delegatfunktionen in eine Bibliothek einfügen, können Sie die Systemreflexion verwenden, um die Verknüpfung zur Laufzeit zu erstellen, und dann die Option haben, den Delegaten an einen Compiler-definierten Delegaten zu übertragen (der wiederum in der Bibliothek enthalten wäre). Der einzige Nachteil hierbei ist, dass die Zielmethode ein genau definierter Speicherort sein muss, also keine anonymen Methoden, da deren Speicherort bei der Kompilierung jedes Mal beim Kompilieren definiert wird. Hier ist der Code, den ich ausgearbeitet habe, um einen Delegaten zur Laufzeit neu erstellen zu können, der auf eigenes Risiko verwendet wird und nicht mit Kommentaren dokumentiert ist.

Update: Eine andere Sache, die Sie tun könnten, ist ein benutzerdefiniertes Attribut zu erstellen und das auf alle Methoden anzuwenden, die Sie in einem Delegaten erstellen möchten. Wechseln Sie zur Laufzeit mithilfe von system reflect zu den gefundenen exportierten Typen, und wählen Sie dann alle Methoden aus den Typen mit dem benutzerdefinierten Attribut aus. Dies könnte mehr als das sein, was Sie wollten, und würde nur von Nutzen sein, wenn Sie auch einen 'ID'-Wert angeben, so dass es eine logische Möglichkeit gibt, die ID über eine Master-Nachschlagetabelle mit dem gewünschten Delegaten zu verknüpfen.

Ich habe auch gerade den Kommentar bemerkt, dass Sie diesen Ansatz wegen des Risikofaktors aufgegeben haben, ich werde das hier belassen, um noch einen anderen Weg zu bieten.

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); 
      } 
     } 
    }