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

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

Ответы на вопрос(4)

Решение Вопроса

Хотя верно то, что вы можете сериализовать и десериализовать делегат точно так же, как любой другой объект, делегат является указателем на метод внутри программы, который его сериализовал. Если вы десериализуете объект в другой программе, вы получитеSerializationException - если вам повезло.

Например, давайте немного изменим программу Дарина:

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».

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

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

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

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

 Sali Hoo16 июл. 2009 г., 06:46
Наконец я решил не сохранять делегатов в файлах. Сохранение делегатов в файлах приводит к другой проблеме: несколько копий одной и той же функции сохраняются в файле. Скорее (как говорит Роберт), я думаю, что лучше определить массив делегатов и сохранить индекс каждого делегата в файле.
 24 нояб. 2016 г., 11:03
+1 этот ответ избавил меня от боли, связанной с такой ошибкой. Другая причина, по которой делегат может измениться, заключается в использовании другой версии компилятора:stackoverflow.com/a/40780504/66372

я могу неправильно понять, когда вы говорите «сохранить», но местоположение, добавленное к делегату во время выполнения, может больше не существовать, если вы попытаетесь сохранить и восстановить адрес.

 15 июл. 2009 г., 19:44
Если вы используете двоичный сериализатор, делегат будет сериализован, а также весь граф объекта, на который он ссылается. Это гарантирует, что делегат может быть вызван после десериализации.
 Sali Hoo15 июл. 2009 г., 19:22
Спасибо Квинтин. Вы правы, как указатель мы не можем. Но как насчет их содержания? что-нибудь вроде оператора C ++ *.

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.

 15 июл. 2009 г., 20:05
Работает также с нестатическими методами. Он также сериализует граф экземпляра Target, предполагая, что он сериализуем (помечен с помощью SerializableAttribute).
 15 июл. 2009 г., 19:53
Вы уверены, что это работает, когда делегат ссылается на нестатический метод? Я вижу, как он работает со статическими методами, так как не нужно определять Traget, но, например, методы, что он делает? Потенциально он мог бы сериализовать граф экземпляра Target (при условии, что он сериализуем), но затем при десериализации и вызове он был бы в другом экземпляре с потенциально устаревшими данными. Я лично был бы очень осторожен при выборе сохранения делегатов таким образом, поскольку это могло легко привести к некоторым неожиданным и трудным для отладки / исправления поведениям.

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

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

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

Ваш ответ на вопрос