在C#中使用免费泛型类型参数模拟委托

时间:2012-10-14 02:42:18

标签: c# generics delegates function-pointers strong-typing

这是一个关于语言设计,模式和语义的难题。请不要因为没有看到实用价值而进行投票。

首先,让我们考虑一下函数及其参数。然后我们将看看函数与它们的参数/参数和泛型类/函数及其类型参数/类型参数之间的类比。

函数是代码块,其中包含一些名为“ parameters ”的未指定值。 您提供参数并接收结果。

通用类是具有一些未指定的“类型参数”的类。 您提供了 type-arguments ,然后您可以使用该类 - 调用构造函数或调用静态方法。

非泛型类中的

泛型函数是具有一些未指定的“ type-parameters ”的函数和一些未指定的“ value-parameters ”。 您提供类型参数值参数以接收结果。

代表是指向特定功能的指针。创建委托时,不要指定函数 arguments ,而是稍后提供它们。

问题在于.Net没有与具有未指定泛型类型参数的泛型函数的Delegates等效。您以后无法为 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);

下面是我想用C#编写的程序的[伪]代码。我想知道如何在正确的C#程序中实现类似的行为。

我们如何在C#中使用免费的通用类型参数模拟委托?

我们如何通过非通用代码将引用/链接传递给具有未知通用参数的泛型函数[s]?

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
    }
}

看看我们如何将对泛型函数(Factory.CreateList和Factory.CreateSet)的引用传递给Worker类构造函数而不指定类型参数?稍后在使用具体类型的数组调用通用DoWork函数时提供类型参数。 DoWork使用 type-arguments 选择正确的函数,将 value-arguments 传递给它并返回接收的值。

最终解决方案: Emulating delegates with free generic type parameters in C#

3 个答案:

答案 0 :(得分:8)

我认为你在语言中模仿这种方式的方法是不使用委托而是使用接口。非泛型接口可以包含泛型方法,因此您可以使用开放类型参数获取委托的大部分行为。

以下是您重新设计为有效C#程序的示例(请注意,它仍然需要您定义的Factory类):

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);
    }
}

您仍然可以获得将要调用的方法与执行所述方法的决策分开的重要功能。

然而,您不能做的一件事是以通用方式将任何状态存储在IWorker实现中。我不确定它是如何有用的,因为每次都可以使用不同的类型参数调用DoWork方法。

答案 1 :(得分:2)

在.Net的类型系统下,这实际上没有意义。

您所描述的是类型构造函数 - 一个“函数”,它采用一种或多种类型并返回具体的(参数化关闭< / em>)输入。

问题是类型构造函数本身不是类型。您不能拥有开放类型的对象或变量;类型构造函数只能用于生成具体类型。

换句话说,无法在.Net的类型系统中表示对开放函数的引用。


你能做的最好就是用反射; MethodInfo可以描述一种开放的通用方法 通过编写一个带有伪通用参数的表达式树的泛型方法,您可以获得对开放式MethodInfo的编译时类型安全引用:

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

GetMethod<string>(() => SomeMethod<string>(...));

如果要引用具有该参数约束的开放泛型方法,则TPlaceholder参数是必需的;您可以选择符合约束条件的占位符类型。

答案 2 :(得分:2)

解决方案是接口。正如@ mike-z所写,接口支持泛型方法。因此,我们可以使用泛型方法创建非泛型接口IFactory,该方法在某些类中包含对泛型方法的引用。要使用这样的接口绑定[Factory]类的泛型方法,我们通常需要创建实现IFactory接口的小类。它们就像lambdas使用的闭包一样。

我认为这与我要求的泛型方法委托之间没有很大的语义差异。解决方案非常类似于编译器为lambdas做的[只调用其他方法](使用调用它的方法创建闭包)。

我们失去了什么?主要是语法糖。

  • 匿名函数/ lambda。我们无法创建通用lambdas。存在 能够创建匿名类(如在Java中)会解决问题 问题。但是,作为lambdas开始,这不是一个大问题 只是.Net中的语法糖。

  • 能够从方法组隐式创建委托/链接(C# 术语)。如果它是通用的,我们不能以任何方式使用方法组。 这也不会影响语义。

  • 阻止定义通用委托的能力。我们做不了 方法IFactory<U, V>的通用V<T> Create<T>(U<T> arg)接口。这也不是问题。

这是解决方案的代码。问题中的Factory类没有改变。

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
    }
}