Можем ли мы сохранить делегатов в файле (С#)

У меня есть класс, который имеет делегат. Я могу установить делегат для каждого экземпляра объекта этого класса, но не нашел способа сохранить этот объект еще

Ответ 1

Это довольно рискованная вещь.

Хотя верно, что вы можете сериализовать и десериализовать делегат точно так же, как и любой другой объект, делегат является указателем на метод внутри программы, который его сериализовал. Если вы десериализуете объект в другой программе, вы получите 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".

Это происходит потому, что то, что на самом деле является сериализованным, - это имя, которое компилятор присваивает выражению лямбда. И компилятор присваивает имена лямбда-выражениям в том порядке, в котором они находятся.

И что в этом рискованно: вы не сериализуете делегата, вы сериализуете символ. Это значение символа, а не то, что символизирует символ, который сериализуется. Поведение десериализованного объекта зависит от того, какое значение этого символа представляет в программе, которое десериализует его.

В определенной степени это верно при всей сериализации. Дезертициализация объекта в программу, которая реализует класс объекта по-разному, чем программа сериализации, и начинается веселье. Но сериализация делегатов связывает сериализованный объект с таблицей символов программы, которая сериализовала ее, а не для реализации класса объекта.

Если бы это был я, я бы подумал о том, чтобы сделать эту связь явной. Я бы создал статическое свойство Foo, которое было Dictionary<string, Func<string>>, заполнило это с помощью ключей и функций и сохранил ключ в каждом экземпляре, а не в функции. Это делает программу десериализации ответственной за заполнение словаря, прежде чем он начнет десериализацию объектов Foo. В какой-то степени это то же самое, что и использование BinaryFormatter для сериализации делегата; разница в том, что этот подход делает работу десериализационной программы для назначения функций символам намного более очевидной.

Ответ 2

На самом деле вы можете с 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.

Ответ 3

Делегат - это указатель на метод, я могу неправильно понять, когда вы скажете "сохранить", но местоположение, добавленное в делегат во время выполнения, может не существовать больше, если вы попытаетесь сохранить и восстановить адрес.

Ответ 4

Итак, я понимаю, что вы хотите "сохранить" указатель на функцию (делегат). Теперь, если вы поместили все свои функции делегата в библиотеку, вы могли бы использовать отражение системы для создания ссылки во время выполнения, а затем иметь возможность передать делегата определенному делегату-компилятору (который снова будет в библиотеке). Единственное падение к этому состоит в том, что целевой метод должен быть четко определенным местом, поэтому никакие анонимные методы, так как там местоположение определяется во время компиляции каждый раз при компиляции. Вот код, который я разработал, чтобы иметь возможность воссоздать делегата во время выполнения, использовать на свой страх и риск и не документировать его комментариями.

Обновление. Еще одна вещь, которую вы можете сделать, - создать пользовательский атрибут и применить его ко всем и всем методам, которые вы хотите создать в делегате. Во время выполнения, используя системное отражение, перемещайте найденные типы экспорта, а затем выберите все методы из тех типов, у которых есть настраиваемый атрибут. Это может быть больше того, что вы хотели, и было бы полезно, если бы вы также предоставили значение "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 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);
            }
        }
    }