我们可以将委托保存在文件中吗(C#)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1132702/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 08:59:39  来源:igfitidea点击:

Could we save delegates in a file (C#)

c#delegates

提问by Sali Hoo

I have a class which has a delegate member. I can set the delegate for each instantiated object of that class but has not found any way to save that object yet

我有一个有代表成员的班级。我可以为该类的每个实例化对象设置委托,但还没有找到任何方法来保存该对象

采纳答案by Robert Rossney

This is a pretty risky thing to do.

这是一件非常冒险的事情。

While it's true that you can serialize and deserialize a delegate just like any other object, the delegate is a pointer to a method inside the program that serialized it. If you deserialize the object in another program, you'll get a SerializationException- if you're lucky.

虽然您确实可以像任何其他对象一样序列化和反序列化委托,但委托是指向序列化它的程序内部方法的指针。如果你在另一个程序中反序列化对象,你会得到一个SerializationException- 如果你很幸运。

For instance, let's modify darin's program a bit:

例如,让我们稍微修改一下 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;
   }
}

Run it, and you'll see that it creates the object, serializes it, deserializes it into a new object, and when you call Delon the new object it returns "a". Excellent. Okay, now comment out the call to WriteFoo, so that the program it's just deserializing the object. Run the program again and you get the same result.

运行它,你会看到它创建了对象,序列化它,将它反序列化为一个新对象,当你调用Del新对象时,它返回“a”。优秀。好的,现在注释掉对 的调用WriteFoo,以便程序只是反序列化对象。再次运行该程序,您会得到相同的结果。

Now swap the declaration of a and b and run the program. Yikes. Now the deserialized object is returning "b".

现在交换 a 和 b 的声明并运行程序。哎呀。现在反序列化的对象返回“b”。

This is happening because what's actually being serialized is the name that the compiler is assigning to the lambda expression. And the compiler assigns names to lambda expressions in the order it finds them.

发生这种情况是因为实际序列化的是编译器分配给 lambda 表达式的名称。并且编译器按照它找到的顺序为 lambda 表达式分配名称。

And that's what's risky about this: you're not serializing the delegate, you're serializing a symbol. It's the valueof the symbol, and not what the symbol represents, that gets serialized. The behavior of the deserialized object depends on what the value of that symbol represents in the program that's deserializing it.

这就是这样做的风险所在:您不是在序列化委托,而是在序列化一个符号。序列化的是符号的,而不是符号所代表的内容。反序列化对象的行为取决于该符号的值在反序列化它的程序中代表什么。

To a certain extent, this is true with all serialization. Deserialize an object into a program that implements the object's class differently than the serializing program did, and the fun begins. But serializing delegates couples the serialized object to the symbol table of the program that serialized it, not to the implementation of the object's class.

在某种程度上,所有序列化都是如此。将一个对象反序列化为一个程序,该程序实现对象的类与序列化程序所做的不同,然后乐趣就开始了。但是序列化委托将序列化的对象耦合到序列化它的程序的符号表,而不是对象的类的实现。

If it were me, I'd consider making this coupling explicit. I'd create a static property of Foothat was a Dictionary<string, Func<string>>, populate this with keys and functions, and store the key in each instance rather than the function. This makes the deserializing program responsible for populating the dictionary before it starts deserializing Fooobjects. To an extent, this is exactly the same thing that using the BinaryFormatterto serialize a delegate is doing; the difference is that this approach makes the deserializing program's responsibility for assigning functions to the symbols a lot more apparent.

如果是我,我会考虑使这种耦合明确。我想创建一个静态属性Foo,这是一个Dictionary<string, Func<string>>与键和功能填充此,并存储在每个实例,而不是功能的关键。这使得反序列化程序负责在开始反序列化Foo对象之前填充字典。在某种程度上,这与使用BinaryFormatter序列化委托所做的完全相同;不同之处在于这种方法使反序列化程序为符号分配函数的责任更加明显。

回答by Darin Dimitrov

Actually you can with BinaryFormatteras it preserves type information. And here's the proof:

实际上,您可以使用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";
    }

}

An important thing you should be aware of if you decide to use BinaryFormatter is that its format is not well documented and the implementation could have breaking changes between .NET and/or CLR versions.

如果您决定使用 BinaryFormatter,您应该注意的一件重要事情是它的格式没有很好的文档记录,并且实现可能在 .NET 和/或 CLR 版本之间发生重大变化。

回答by Quintin Robinson

A delegate is a method pointer, I might misunderstand when you say save, but the location added to the delegate at runtime might not exist any longer if you try and save and restore the address.

委托是一个方法指针,当您说保存时我可能会误解,但是如果您尝试保存和恢复地址,则在运行时添加到委托的位置可能不再存在。

回答by Joe Colvin

So, it is my understanding that you want to 'save' a function pointer (delegate). Now, if you put all your delegate functions into a library you could use system reflection to build the link at runtime and then have the choice to cast the delegate to a compiler defined delegate (which, again would be in the library). The Only downfall to this is that the target method has to be a well defined location, so no anonymous methods since there location are defined at compile time each and every time you compile. Here is the code that I worked out to be able to recreate a delegate at runtime, use at your own risk and its not documented with comments.

所以,我的理解是你想“保存”一个函数指针(委托)。现在,如果您将所有委托函数放入一个库中,您可以使用系统反射在运行时构建链接,然后可以选择将委托转换为编译器定义的委托(该委托也将在库中)。唯一的缺点是目标方法必须是一个明确定义的位置,因此没有匿名方法,因为每次编译时都会在编译时定义位置。这是我制定的代码,以便能够在运行时重新创建委托,使用风险由您自己承担,并且没有用注释记录。

Update: Another thing that you could do is to create a custom attribute and apply that to any and all methods that you want to have created into a delegate. At runtime, using system reflect, traverse the exported types found and then select all of the methods from those types that have the custom attribute. That might be more then what you wanted and would only be of use if you also supplied an 'ID' value so there was a logical way of linking the id to the desired delegate via a master look up table.

更新:您可以做的另一件事是创建一个自定义属性并将其应用于您想要创建到委托中的任何和所有方法。在运行时,使用系统反射,遍历找到的导出类型,然后从具有自定义属性的类型中选择所有方法。这可能比您想要的更多,并且只有在您还提供了“ID”值时才有用,因此有一种逻辑方式可以通过主查找表将 id 链接到所需的委托。

I also just noticed the comment that you had given up on this approach due to the risk factor, I'll leave this here to provide yet another way of doing things.

我也刚刚注意到您由于风险因素而放弃这种方法的评论,我将把它留在这里以提供另一种做事方式。

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