Podríamos guardar delegates en un archivo (C #)

Tengo una clase que tiene un miembro delegado. Puedo configurar el delegado para cada objeto instanciado de esa clase, pero no he encontrado ninguna manera de guardar ese objeto aún

Esto es algo muy arriesgado de hacer.

Si bien es cierto que puede serializar y deserializar un delegado al igual que cualquier otro objeto, el delegado es un puntero a un método dentro del progtwig que lo serializó. Si deserializa el objeto en otro progtwig, obtendrá una SerializationException , si tiene suerte.

Por ejemplo, modifiquemos un poco el progtwig de darin:

 class Program { [Serializable] public class Foo { public Func Del; } static void Main(string[] args) { Func a = (() => "a"); Func 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; } } 

Ejecútelo y verá que crea el objeto, lo serializa, lo deserializa en un nuevo objeto y cuando llama a Del en el nuevo objeto, devuelve “a”. Excelente. De acuerdo, ahora comenta la llamada a WriteFoo , para que el progtwig simplemente deserialice el objeto. Ejecute el progtwig nuevamente y obtendrá el mismo resultado.

Ahora intercambie la statement de ayb y ejecute el progtwig. Yikes. Ahora el objeto deserializado está regresando “b”.

Esto está sucediendo porque lo que en realidad se está serializando es el nombre que el comstackdor está asignando a la expresión lambda. Y el comstackdor asigna nombres a las expresiones lambda en el orden en que las encuentra.

Y eso es lo arriesgado de esto: no estás serializando al delegado, estás serializando un símbolo. Es el valor del símbolo, y no lo que representa el símbolo, lo que se serializa. El comportamiento del objeto deserializado depende de lo que el valor de ese símbolo represente en el progtwig que lo está deserializando.

Hasta cierto punto, esto es cierto con todas las serializaciones. Deserializar un objeto en un progtwig que implementa la clase del objeto de forma diferente a como lo hizo el progtwig de serialización, y comienza la diversión. Pero la serialización de delegates acopla el objeto serializado a la tabla de símbolos del progtwig que lo serializó, no a la implementación de la clase del objeto.

Si fuera yo, consideraría hacer este acoplamiento explícito. Creé una propiedad estática de Foo que era un Dictionary> , lo llenaba con teclas y funciones, y almacenaba la clave en cada instancia en lugar de la función. Esto hace que el progtwig de deserialización se encargue de poblar el diccionario antes de que comience a deserializar objetos Foo . Hasta cierto punto, esto es exactamente lo mismo que usar BinaryFormatter para serializar un delegado; la diferencia es que este enfoque hace que la responsabilidad del progtwig de deserialización para asignar funciones a los símbolos sea mucho más evidente.

En realidad, puedes hacerlo con BinaryFormatter ya que conserva la información del tipo. Y aquí está la prueba:

 class Program { [Serializable] public class Foo { public Func 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"; } } 

Una cosa importante que debe tener en cuenta si decide usar BinaryFormatter es que su formato no está bien documentado y la implementación podría tener cambios de última hora entre versiones de .NET y / o CLR.

Un delegado es un puntero de método, podría malinterpretar cuando dices guardar, pero la ubicación añadida al delegado en tiempo de ejecución podría no existir más si intentas guardar y restaurar la dirección.

Por lo tanto, tengo entendido que desea ‘guardar’ un puntero a función (delegado). Ahora, si coloca todas sus funciones de delegado en una biblioteca, podría usar la reflexión del sistema para construir el enlace en tiempo de ejecución y luego tener la opción de convertir el delegado a un delegado definido por el comstackdor (que, de nuevo, estaría en la biblioteca). La única desventaja de esto es que el método de destino debe ser una ubicación bien definida, por lo que no hay métodos anónimos ya que su ubicación se define en tiempo de comstackción cada vez que se comstack. Aquí está el código que desarrollé para poder recrear un delegado en tiempo de ejecución, usarlo bajo su propio riesgo y no está documentado con comentarios.

Actualización: Otra cosa que podría hacer es crear un atributo personalizado y aplicarlo a todos los métodos que desee crear en un delegado. En tiempo de ejecución, utilizando el sistema reflect, recorra los tipos exportados encontrados y luego seleccione todos los métodos de aquellos que tienen el atributo personalizado. Eso podría ser más de lo que quería y solo sería útil si también proporcionó un valor de ‘ID’, por lo que había una forma lógica de vincular el id al delegado deseado a través de una tabla de búsqueda principal.

También noté el comentario de que usted había renunciado a este enfoque debido al factor de riesgo, lo dejaré aquí para proporcionar otra forma de hacer las cosas.

  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 PatwigterInformationLine = GetPatwigterLine(parameters); throw new NotSupportedException( string.Format(ErrorFormatMessage, targetType.Name, PatwigterInformationLine), err); } } private static string GetPatwigterLine(Object[] parameters) { if (parameters == null) return "NONE\n"; string PatwigterFormatLine = "==> Patwigter Type is {0} and object is {1}\n"; string PatwigterInformationLine = string.Empty; foreach (object item in parameters) { PatwigterInformationLine += string.Format(PatwigterFormatLine, item.GetType().Name, item); } return PatwigterInformationLine; } 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); } } }