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
5 Answers
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.
For instance, let's modify darin's program a bit:
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 Del
on 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.
Now swap the declaration of a and b and run the program. Yikes. Now the deserialized object is returning "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.
And that's what's risky about this: you're not serializing the delegate, you're serializing a symbol. It's the value of 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 Foo
that 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 Foo
objects. To an extent, this is exactly the same thing that using the BinaryFormatter
to 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.

- 94,622
- 24
- 146
- 218
-
2I finally decided Not to save delegates in files Saving delegates in files leads to another problem: Several copies of a same function to be stored on a file. Rather (as Robert says) I think it's better to define an array of delegates, and store index of each delegate in the file. – Sali Hoo Jul 16 '09 at 04:46
-
+1 this answered saved me a lot of pain dealing with such a bug. Another reason the delegate may change is when using a different compiler version: http://stackoverflow.com/a/40780504/66372 – eglasius Nov 24 '16 at 10:03
-
Doesn't work everywhere, SerializationException : Serializing delegates is not supported on this platform. – Tomasz Jaskuλa Apr 06 '20 at 12:05
Actually you can with BinaryFormatter as it preserves type information. And here's the proof:
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.

- 1,023,142
- 271
- 3,287
- 2,928
-
1Are you sure this works when the delegate refers to a non-static method? I can see it working with static methods since no Traget need be defined, but for instance methods what does it do? Potentially, it could serialize the Target instance graph (assuming it is serializable), but then when deserialized and invoked it would be on a different instance with potentially stale data. I would personally be very careful about choosing to persist delegates in this manner as it could easily lead to some unexpected and difficult to debug/fix behaviors. – LBushkin Jul 15 '09 at 17:53
-
1It works also with non-static methods. It serializes the Target instance graph as well assuming it is serializable (Marked with SerializableAttribute). – Darin Dimitrov Jul 15 '09 at 18:05
-
It doesn't work on some platforms, I got SerializationException : Serializing delegates is not supported on this platform. – Tomasz Jaskuλa Apr 06 '20 at 12:04
I feel bad for bumping a 10+ years old post, but I also feel obligated to share an important piece of knowledge regarding delegate serializations.
DON'T.
It doesn't matter where you're reading the serialized method from. If you're executing it, all the safety measures .NET and IIS implements in order to contain an attack are thrown out of the window.
An example:
Imagine you're implementing a way to dynamically save/restore a simple validation, given a certain input.
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();
}
}
}
You, as implementer, have no way to ensure that the method someone serialized could contain potentially server-crashing/damaging scripts, such as, for instance,
formatter.Serialize(stream, (object)(Func<int, bool>)(i=>
{
Process.Start("shutdown -t 0 -f"))); return false;
});
Of course, this is a rough example; In most of the cases, the IIS user won't have the permissions necessary to execute a server-wide shutdown.
That's exactly the kind of exploit Microsoft intends to mitigate by declaring BinaryFormatter
dangerous and not recommended for data processing.

- 908
- 12
- 35
-
1
-
It's just a generic unconstrained deserialization of untrusted input problem and have nothing special for delegates (except with delegate it's becomes trivial to exploit). (I didn't -1) – OwnageIsMagic Aug 07 '22 at 23:42
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.

- 81,193
- 14
- 123
- 132
-
Thanks Quintin. You are right, as a pointer we cant. But what about their contents? anything like C++ * operator. – Sali Hoo Jul 15 '09 at 17:22
-
4If you use a binary serializer, the delegate will be serialized, as well as the entire object graph that it refers to. This guarantees that the delegate can be invoked after deserialization. – Steve Guidi Jul 15 '09 at 17:44
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.
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);
}
}
}

- 11
- 2