我们可以将代理保存在文件中(C#)

时间:2009-07-15 17:11:05

标签: c# delegates

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

5 个答案:

答案 0 :(得分:37)

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

虽然您可以像任何其他对象一样序列化和反序列化委托,但委托是指向序列化程序内部方法的指针。如果你在另一个程序中反序列化该对象,你将得到一个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”。

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

这就是风险:你没有序列化代表,你正在序列化一个符号。它是符号的,而不是符号表示的序列化。反序列化对象的行为取决于该符号的值在反序列化的程序中表示的内容。

在某种程度上,所有序列化都是如此。将对象反序列化为一个程序,该程序以不同于序列化程序的方式实现对象的类,并开始有趣。但序列化委托将序列化对象耦合到序列化它的程序的符号表,而不是对象类的实现。

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

答案 1 :(得分:16)

实际上,您可以使用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 :(得分:2)

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

答案 3 :(得分:1)

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

更新:您可以做的另一件事是创建自定义属性并将其应用于您想要创建到委托中的任何和所有方法。在运行时,使用系统反射,遍历找到的导出类型,然后从具有自定义属性的那些类型中选择所有方法。这可能比您想要的更多,只有在您提供“ID”值时才有用,因此有一种逻辑方法可以通过主查找表将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);
            }
        }
    }

答案 4 :(得分:0)

我为撞到 10 多年的帖子感到难过,但我也觉得有必要分享关于委托序列化的重要知识。

不要。

从哪里读取序列化方法并不重要。如果您正在执行它,则 .NET 和 IIS 为遏制攻击而实施的所有安全措施都将被抛在一边。

示例:

假设您正在实施一种方法,可以在给定特定输入的情况下动态保存/恢复简单验证。

BinaryFormatter formatter = new BinaryFormatter();
byte[] serializedStream  = null;

using(MemoryStream stream = new MemoryStream())
{
    // Someone generates a script, serialize it 
    formatter.Serialize(stream, (object)(Func<int, bool>)(i=> i == 0));
    
    // and save it in a database, for instance.
    serializedStream = stream.ToArray();
}

// Somewhere else, you read the saved byte array
using (MemoryStream stream = new MemoryStream(serializedStream))
{
    // Deserialize it
    if (formatter.Deserialize(stream) is Func<int, bool> funcao)
    {
        try
        {
            // Execute it with a given input
            funcao(1).Dump();
        }
        // And catches the exceptions for good measure.
        catch(Exception e)
        {
            "Exception occurred".Dump();
        }
    }
}

作为实施者,您无法确保某人序列化的方法可能包含潜在的服务器崩溃/损坏脚本,例如

formatter.Serialize(stream, (object)(Func<int, bool>)(i=> 
{
    Process.Start("shutdown -t 0 -f"))); return false;
});

当然,这是一个粗略的例子;在大多数情况下,IIS 用户没有执行服务器范围关闭所需的权限。

这正是微软打算通过声明 BinaryFormatter

<块引用>

危险,不推荐用于数据处理。

https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide#binaryformatter-security-vulnerabilities