Delegate emulation with free typical type parameters in C #

This is a tricky question about the design, patterns, and semantics of the language. Please do not vote because you do not see practical value.

First, think about functions and their parameters. Then we look at the analogy between functions with their parameters / arguments and general classes / functions with their type parameters / type arguments.

Functions are blocks of code with some unspecified values ​​called "parameters". You pass arguments and get the result.

General classes are classes with some undefined "type parameters". You provide type arguments, and then you can work with the class - call the constructor or call static methods.

Common functions in nonequivalent classes are functions with some undefined "type parameters" and some undefined "parameter values". You get type arguments and value arguments to get the result.

Delegates are pointers to specific functions. When you create a delegate, you do not specify function arguments, but supply them later.

The problem is that .Net does not have a delegate equivalent for common functions with undefined typical type parameters. You cannot specify type values ​​for type parameters later. We can imagine delegates who have not only free parameter values, but also free type parameters.

static class SomeClass { //generic function public static T GetValue<T>() { return default(T); } } //creating delegate to generic function or method group Func{TFree}<TFree> valueFactory = SomeClass.GetValue; //creating delegate to anonymous generic function Func{TFree}<int, List<TFree>> listFactory = {TFree}(int capacity) => new List<TFree>(capacity); 

Below is the [pseudo] code for the program I want to write in C #. I want to know how you can achieve similar behavior in the right C # program.

How can we emulate delegates with free type type parameters in C #?

How can we pass a link / link to a common function [s] with still unknown common parameters through non-common code?

 public static class Factory { //Everything compiles fine here public delegate ICollection<T> FactoryDelegate<T>(IEnumerable<T> values); public static ICollection<T> CreateList<T>(IEnumerable<T> values) { return new List<T>(values); } public static ICollection<T> CreateSet<T>(IEnumerable<T> values) { return new HashSet<T>(values); } } public class Worker { //non-generic class Func{TFree}<FactoryDelegate<TFree>> _factory; //TFree is a "free" generic type paramenter public Worker(Func{TFree}<FactoryDelegate<TFree>> factory) { _factory = factory; } public ICollection<T> DoWork<T>(IEnumerable<T> values) { //generic method return _factory{T}(values); //supplying T as the argument for type parameter TFree } } public static class Program { public static void Main() { string[] values1 = new string[] { "a", "b", "c" }; int[] values2 = new int[] { 1, 2, 2, 2 }; Worker listWorker = new Worker(Factory.CreateList); //passing reference to generic function Worker setWorker = new Worker(Factory.CreateSet); //passing reference to generic function ICollection<string> result1 = listWorker.DoWork(values1); ICollection<int> result2 = listWorker.DoWork(values2); //.Count == 4 ICollection<int> result3 = setWorker.DoWork(values2); //.Count == 2 } } 

See how we pass references to common functions (Factory.CreateList and Factory.CreateSet) to the constructor of the Worker class without specifying type arguments? Type arguments are passed later when the generic DoWork function is called with specific typed arrays. DoWork uses type arguments to select the correct function, passes argument values ​​to it, and returns the resulting value.

Final Solution: Delegate Emulation with Free Typical Type Parameters in C #

+8
generics c # delegates function-pointers strong-typing
source share
3 answers

I think that you emulate this in this language, not using delegates, but interfaces. A non-generic interface may contain a generic method, so you can get most of the behavior of delegates with open arguments.

Here is your example reprocessed in a valid C # program (note that it still requires the Factory class that you defined):

 public interface IWorker { ICollection<T> DoWork<T>(IEnumerable<T> values); } public class ListCreationWorker : IWorker { public ICollection<T> DoWork<T>(IEnumerable<T> values) { return Factory.CreateList<T>(values); } } public class SetCreationWorker : IWorker { public ICollection<T> DoWork<T>(IEnumerable<T> values) { return Factory.CreateSet<T>(values); } } public static class Program { public static void Main(string[] args) { string[] values1 = new string[] { "a", "b", "c" }; int[] values2 = new int[] { 1, 2, 2, 2 }; IWorker listWorker = new ListCreationWorker(); IWorker setWorker = new SetCreationWorker(); ICollection<string> result1 = listWorker.DoWork(values1); ICollection<int> result2 = listWorker.DoWork(values2); //.Count == 4 ICollection<int> result3 = setWorker.DoWork(values2); //.Count == 2 } } public static class Factory { public static ICollection<T> CreateSet<T>(IEnumerable<T> values) { return new HashSet<T>(values); } public static ICollection<T> CreateList<T>(IEnumerable<T> values) { return new List<T>(values); } } 

You still get the important function of splitting the decision about which method is invoked when the specified method is executed.

However, one thing you cannot do is to keep any state in IWorker implementations in a general way. I'm not sure how this can be useful, because the DoWork method can be called with different type arguments every time.

+7
source share

It really doesn't make sense in a system like .Net.

What you are describing is a type constructor - a β€œfunction” that takes one or more types and returns a specific (parameterized or closed) type.

The problem is that type constructors themselves are not types. You cannot have an object or variable of an open type; type constructors can only be used to generate specific types.

In other words, it is impossible to provide a link to an open function in a system such as .Net.


The best you can do is use reflection; a MethodInfo can describe an open general method.
You can get a binding to the type of the compile-time type on the open MethodInfo by writing a generic method that accepts an expression tree with a fake generic parameter:

 public MethodInfo GetMethod<TPlaceholder>(Expression<Action> method) { //Find the MethodInfo and remove all TPlaceholder parameters } GetMethod<string>(() => SomeMethod<string>(...)); 

The TPlaceholder parameter TPlaceholder required if you want to reference an open general method with a restriction on this parameter; You can select the type of placeholder that matches the restriction.

+2
source share

The solution is interfaces. As @ mike-z wrote, interfaces support common methods. Thus, we can create a non-common IFactory interface using a universal method that encapsulates a reference to a common method in some class. To bind the general method of the [Factory] class using such an interface, we usually need to create small classes that implement the IFactory interface. They act like spells used by lambdas.

I do not see much semantic difference between this and the delegates of the generalized method that I requested. The solution is very similar to what the compiler does for lambdas [, which simply calls other methods] (create a closure using the method that calls).

What are we losing? Mostly syntactic sugar.

  • Anonymous functions / lambdas. We cannot create generic lambdas. being able to create anonymous classes (e.g. in Java) would solve the problem. But this is not a big problem since lambda is just syntactic sugar in .Net.

  • Ability to implicitly create a delegate / link from a group of methods (C # term). We cannot use a group of methods in any way if it is common. It also does not affect semantics.

  • The difficulty of defining common delegates is difficult. We cannot make a common IFactory<U, V> interface with the V<T> Create<T>(U<T> arg) method. This is not a problem either.

This is the solution code. The Factory class from the question does not change.

 public interface IFactory { ICollection<T> Create<T>(IEnumerable<T> values); } public class Worker { //not generic IFactory _factory; public Worker(IFactory factory) { _factory = factory; } public ICollection<T> DoWork<T>(IEnumerable<T> values) { //generic method return _factory.Create<T>(values); } } public static class Program { class ListFactory : IFactory { public ICollection<T> Create<T>(IEnumerable<T> values) { return Factory.CreateList(values); } } class SetFactory : IFactory { public ICollection<T> Create<T>(IEnumerable<T> values) { return Factory.CreateSet(values); } } public static void Main() { string[] values1 = new string[] { "a", "b", "c" }; int[] values2 = new int[] { 1, 2, 2, 2 }; Worker listWorker = new Worker(new ListFactory()); Worker setWorker = new Worker(new SetFactory()); ICollection<string> result1 = listWorker.DoWork(values1); ICollection<int> result2 = listWorker.DoWork(values2); //.Count == 4 ICollection<int> result3 = setWorker.DoWork(values2); //.Count == 2 } } 
+2
source share

All Articles