Можем ли мы сохранить делегатов в файл (C #) - PullRequest
19 голосов
/ 15 июля 2009

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

Ответы [ 4 ]

37 голосов
/ 15 июля 2009

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

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

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

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

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

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

16 голосов
/ 15 июля 2009

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

2 голосов
/ 15 июля 2009

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

1 голос
/ 25 июня 2011

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

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