Could We Save Delegates in a File (C#)

Could we save delegates in a file (C#)

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.

Where do I put my C# delegate declaration, in a file of its own?

I tend to put each type in a separate code file. Doing so will help you navigate in Solution Explorer, though ReSharper rocks with "Go To File"

alt text
(source: jetbrains.com)

and "Go To Type":

alt text
(source: jetbrains.com)

Write Delegates without messing up the code

You're declaring two very different things here:

  • The first line declares a delegate type called MyMethod
  • The second line declares a field of that delegate type

It's important to understand the difference, because then you can work out when you really want to declare a new delegate type and when you just want to declare a field of an existing delegate type. If your class has 20 delegate fields, you almost certainly don't want to declare a new type for each of them. If they've got the same signature, you could use a single type... or better, just use one of the framework types such as Action<...> or Func<...>.

Action<string, string> mm;

(There are Action delegates for void return types, and Func delegates for non-void return types, with different numbers of parameters, all expressed generically. Look at MSDN for more details.)

How to store delegates in a List

Does System.Collections.Generic.Dictionary<string, System.Delegate> not suffice?

Should a delegate be declared inside the class that will raise the event, or outside?

Typically, these days you'd create your own class derived from EventArgs, and then just use EventHandler<TEventArgs> - there's no need to create a separate delegate type. So instead of AlarmEventHandler, you'd use EventHandler<AlarmEventArgs>. The EventArgs-derived class should generally be top-level (i.e. non-nested).

Where to put Delegates in .Net Solution

I would assume the general consensus is that you place them anywhere
that makes sense for the use case, but in general I feel there is a
best way to structure everything in a solution to minimise problems,
promote positive modularity, and ease maintenance.

Well yes, in the general case, you can just put everything wherever you please it. However, the problems start when you're working in a team. In a team, other people have to understand the structure of the program to be able to create code effectively. That implies consistency, which is the end goal.

So, it's not just a matter of intuition; in all the companies that I've worked in, I've started by writing a coding standard and aggressively enforcing it everywhere. In this coding standard, I usually put a ton of good/bad practices involving threading, static use, placement of variables / classes, etc.

Iirc this link http://se.inf.ethz.ch/old/teaching/ss2007/251-0290-00/project/CSharpCodingStandards.pdf and https://msdn.microsoft.com/en-us/library/ff926074.aspx will give you a head start, although the former is a bit outdated and both don't really answer your question. It might however help with what you're trying to achieve in the long run.

[...] I currently have delegates in
their own file, in a 'delegate' namespace. Is there an oversight I am
making by doing so, or am I overcomplicating the use case for
delegates - or is this inline with current usage expectations?

A common library with interfaces makes sense. An interface is essentially a contract between 'client' and 'server', which is an important asset for decoupling a piece of software.

You have to realize at this point that the 'interface' part is a functional decomposition, not a technical one. In other words: the fact that you need an 'interface' library is a matter of contract, not of technical constructs. As such, I don't see why you would ever put classes in a 'classes' folder and why you would put delegates in a 'delegates' folder. In short, a 'delegates' folder doesn't make any sense to me. Put the stuff where it belongs.

At this point, you have a decision to make. (1) You can put a delegate in a single file, (2) you can put a delegate in the class file where it's used (I usually use delegates for a single purpose), (3) you can do both depending on single / multi purpose and (4) you can put single-purpose delegates nested within the class. This basically means you're treating a delegate as a function pointer description, and don't see it as a real 'class'.

Looking at functional decomposition, you can argue that a function pointer belongs to a class, just like methods.

So to concluse. Personally I usually go for option (2) or (3) because it's brief, clear and concise, and doesn't have an impact on the amount of 'typing work' like (4) . Picking (1) will probably bloat your application with small files that have little or no purpose, which will confuse other developers.

Persist object with delegate property

I ended up serialising it to a binary stream and saving that. Kind of gross, but its working.

    public byte[] callbackData { get; set; }

/// <summary>
/// Delegate to call back
/// </summary>
[NotMapped]
public Callback callback
{
get
{
if (_callback == null && callbackData != null && callbackData.Count() > 0)
{
BinaryFormatter formatter = new BinaryFormatter();
using (var stream = new MemoryStream(callbackData))
{
_callback = formatter.Deserialize(stream) as Callback;
}
}
return _callback;
}
set
{
_callback = value;
if (value == null)
{
callbackData = null;
}
else
{
BinaryFormatter formatter = new BinaryFormatter();
using (var stream = new MemoryStream())
{
formatter.Serialize(stream, value);
callbackData = stream.ToArray();
}
}
}
}


Related Topics



Leave a reply



Submit