Show / Hide Table of Contents

Open and Closed Delegates

Common Language Runtime supports open and closed delegates but there is no native support of them at the syntax level in C#. Java supports method references to do that. C# syntactically supports closed delegate based created from instance method or open delegate created from static method.

Additionally, there is no way to create a delegate instance for a property getter, indexer or overloaded implicit/explicit type cast operator. DelegateHelpers class offers two methods to provide necessary syntactic sugar:

  • CreateOpenDelegate to create open delegate based on instance method, property, indexer, or overloaded operator
  • CreateClosedDelegateFactory to create closed delegate based on static method, indexer, or overloaded operator

Open Delegates

Open delegate allows to pass this argument explicitly. The only way to do that in C# is to use lambda expression or static method to express this capability:

using System.Collections;

var sizeOf = new Func<ICollection, int>(collection => collection.Count);

This approach generates unecessary hidden static method which is used to create instance of Predicate delegate. CreateOpenDelegate allows to create open delegate without counter-intuitive usage of Delegate.CreateDelegate.

using DotNext;
using System.Collections;

var sizeOf = DelegateHelpers.CreateOpenDelegate<Func<ICollection, int>>(collection => collection.Count);

sizeOf delegate instance representing get_Count getter method directly instead of wrapping it into static method.

Warning

CreateOpenDelegate method utilizes Expression Trees feature of C# programming language. There is no free lunch and you paying for such a convenience by performance when instantiating delegate instead of CreateDelegate. That's why it is recommended to create such delegates statically and save them into static readonly fields. However, CreateOpenDelegate doesn't use dynamic code compilation feature.

This method is suitable for capturing overloaded operator.

var decimalToInt = DelegateHelpers.CreateOpenDelegate<Func<int, decimal>>(i => (decimal) i);

decimal d = decimalToInt(42);

Built-in operators cannot be captured as delegates and method returns null.

Closed Delegates

Closed delegates allow to pass first argument to the underlying method implicitly (through Target property). The only way to create closed delegate in C# is to use extension method or closure:

var str = "Hello, world!";
var isEmpty = new Func<bool>(() => string.IsNullOrEmpty(str));

Roslyn Compiler generates anonymous type to capture str local variable and instance method in such type that matches to signature of delegate Func<bool>.

You can avoid this with some trick from .NEXT library:

using DotNext;

var str = "Hello, world";
Func<bool> isEmpty = new Func<string, bool>(string.IsNullOrEmpty).Method.CreateDelegate<Func<bool>>(str);

Now it looks better, but we loosing readability of code. The same behavior can be achieved using CreateClosedDelegateFactory:

using DotNext;

var str1 = "";
var str2 = "Hello, world";

var factory = DelegateHelpers.CreateClosedDelegateFactory<Func<bool>>(() => string.IsNullOrEmpty(default(string)));
Func<bool> isEmpty1 = factory(str1);
Func<bool> isEmpty2 = factory(str2);

The factory returned by method CreateClosedDelegateFactory can be used to create closed delegate instances through passing first implicit argument to it. This approach doesn't produce anonymous type. Instead, captured argument is stored in Target property and passed to the method automatically by CLR.

  • Edit this page
☀
☾
In this article
Back to top
Supported by the .NET Foundation
☀
☾