У меня есть класс, который имеет делегат. Я могу установить делегат для каждого экземпляра объекта этого класса, но не нашел способа сохранить этот объект еще
Можем ли мы сохранить делегатов в файле (С#)
Ответ 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);
}
}
}