Emulating delegates with free generic type parameters in C#
Asked Answered
L

3

9

This is a hard question about language design, patterns and semantics. Please, don't down-vote just because you don't see the practical value.

First, let's think about functions and their parameters. Then we'll look at the analogies between functions with their parameters/arguments and generic classes/functions with their type-parameters/type-arguments.

Functions are blocks of code with some unspecified values called "parameters". You supply the arguments and receive the result.

Generic classes are classes with some unspecified "type-parameters". You supply the type-arguments and then you can work with the class - call the constructor or invoke static methods.

Generic functions in non-generic classes are functions with some unspecified "type-parameters" and some unspecified "value-parameters". You supply the type-arguments and value-arguments to receive result.

Delegates are pointers to specific functions. When you create delegate you don't specify the function arguments, but supply them later.

The problem is that .Net doesn't have equivalent of Delegates for generic functions with unspecified generic type-parameters. You cannot supply type-values for the type-parameters later. We can imagine delegates that have not only free value parameters, 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 a program that I want to write in C#. I want to know how one can achieve the similar behavior in a correct C# program.

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

How can we pass the reference/link to generic function[s] with yet-unknown generic parameters through the non-generic 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 the references to generic functions (Factory.CreateList and Factory.CreateSet) to the Worker class constructor without specifying the type arguments? Type arguments are supplied later when the generic DoWork function is called with concrete-typed arrays. DoWork uses the type-arguments to select the correct function, passes value-arguments to it and returns the received value.

Final solution: Emulating delegates with free generic type parameters in C#

Landonlandor answered 14/10, 2012 at 2:42 Comment(3)
See the terms open type and especially type constructor.Accumulative
I don't think C# has any way to do this without using reflection or making Worker generic. I suspect that higher-kinded types, which .Net does not support, would help.Accumulative
I'm not sure, but have you looked at F# instead? Forcing C# to be more functional will probably be painful execrcise.Butterflies
C
9

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

Here is your example re-worked into a valid C# program (Note that it still requires the Factory class 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 feature of separating the decision of which method to call from the execution of said method.

One thing that you cannot do, however, is store any state in the the IWorker implementations in a generic fashion. I'm not sure how that could be useful because the DoWork method could be called with different type arguments every time.

Claire answered 14/10, 2012 at 3:40 Comment(5)
FYI, I think you flipped your factory method calls. ListCreationWorker is calling Factory.CreateSet, and SetCreationWorker is calling Factory.CreateList.Sacken
@ChrisSinclair, yes, fixed now.Claire
So simple!!! I need to sleep on this and look at this with a fresh head to make sure I'm not mistaken and this really solves my problem! P.S. It's not Worker who should be interfaced (there only one implementation of Worker.DoWork and undefined number of factories), but Factory. But that doesn't defeat the solution.Landonlandor
I've modified your solution a bit, making interface for factories instead of worker (https://mcmap.net/q/1183593/-emulating-delegates-with-free-generic-type-parameters-in-c). I don't see semantic difference between this and the generic method delegates that I've asked for. We're only losing some syntactic sugar. The solution is very much like what compiler does for lambdas [that just call other methods] (create closure with the method that calls the). Thank a lot.Landonlandor
Years later, still relevant, elegant and brilliant. Indeed much more verbose than an "open" delegate syntax could be, but I can see why this is not the C# team's highest priority feature.Corr
A
2

This does actually not make sense under .Net's type system.

What you're describing is a type constructor – a "function" that takes one or more types and returns a concrete (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 concrete types.

In other words, there is no way to represent a reference to an open function within .Net's type system.


The best you can do is to use reflection; a MethodInfo can describe an open generic method.
You can get a compile-time type-safe reference to an open MethodInfo by writing a generic method that takes 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 is necessary in case you want to reference an open generic method with a constraint on that parameter; you can pick a placeholder type that meets the constraint.

Accumulative answered 14/10, 2012 at 2:53 Comment(7)
Well, I can use open types Type openType = typeof(List<>). This looks like an open type. I can also construct concrete types from open types: Type closedType = typeof(Func<,>).MakeGenericType(typeof(int), typeof(string))Landonlandor
@Ark-kun: You can represent open types using reflection, but not as types within the type system. Func<> is only ever valid in typeof(). Similarly, you can only represent open methods as MethodInfos.Accumulative
I'm trying to think out of the box. Maybe there is some logical technique or pattern that gives me the same behavior (without reflection, dynamic code, code generation and so on).Landonlandor
@Ark-kun: That would depend on what you're actually trying to accomplish. Can you show a sample use-case?Accumulative
Worker.DoWork<T> is a generic function. It could just do 'Factory.CreateList<T>' or 'Factory.CreateSet<T>' without any reflection. If only it knew which one to use... (The number of potential factories is infinite of course)Landonlandor
My use-case is not interesting - I know a dirty way to do it. I want to invent a universal solution. You can look at my other question and my comment to it for one of my concrete use cases #12878050Landonlandor
this is what I was talking about: #12879364 mike-z has found a simple solution that doesn't use reflection.Landonlandor
L
2

The solution is interfaces. As @mike-z wrote, interfaces support generic methods. So, we can create the non-generic interface IFactory with generic method which incapsulates a reference to a generic method in some class. To bind the generic method of a [Factory] class using such interface we usually need to create small classes implementing the IFactory interface. They act just like closures used by lambdas.

I don't see big semantic difference between this and the generic method delegates that I've asked for. The solution is very much like what compiler does for lambdas [that just call other methods] (create closure with the method that calls the).

What are we losing? Mostly syntactic sugar.

  • Anonymous functions/lambdas. We cannot create generic lambdas. Being able to create anonymous classes (like in Java) would have solved the problem. But this isn't much of a problem to begin with as lambdas are just syntactic sugar in .Net.

  • Ability to implicitly create delegate/link from the method group (C# term). We cannot use the method group in any way if it's generic. This doesn't affect the semantics too.

  • Ability to define generic delegates is impeded. We cannot make a generic IFactory<U, V> interface with method V<T> Create<T>(U<T> arg). This is not a problem too.

This is the code of the solution. The Factory class from the question is unchanged.

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
    }
}
Landonlandor answered 17/10, 2012 at 4:9 Comment(4)
It's too bad there's no syntactic sugar for any such constructs, since generic interfaces can allow a method to accept an actor object and its appropriate parameters, without having to wrap the parameters in a delegate and/or closure. They can even pass ref parameters, whereas closures cannot.Signorina
@Signorina by closure, you mean a lambda expression? lambdas do support ref and out parameters. However, you must assign it to an appropriate delegate, which you would have to define. They are not compatible with the built-in Action or Func delegates.Claire
@mikez: Given delegate ActByRef<T1>(ref T1 p1), replacing void DoSomething(Action proc) with void DoSomething<TP1>(IActByRef<TP1> proc, ref TP p1); would allow code which wants to perform an action using local variables to pass a delegate to a static method along with a ref to a struct containing those variables, avoiding the need to create a heap object to hold those variables and another to hold a new delegate bound to it (for the situation where the called method won't have to persist the delegate and associated variables). That part works even with delegates, but...Signorina
...whereas one could create a delegate to the method that took a proc, one cannot create one to the generic method.Signorina

© 2022 - 2024 — McMap. All rights reserved.