9

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

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

函数是具有一些未指定值的代码块,称为“参数”。您提供参数并接收结果。

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

非泛型类中的泛型函数是具有一些未指定的“类型参数”和一些未指定的“值参数”的函数。您提供类型参数值参数来接收结果。

委托是指向特定函数的指针。创建委托时,您不指定函数参数,而是稍后提供它们。

问题是 .Net 对于具有未指定泛型类型参数的泛型函数没有等效的 Delegates 。以后不能为类型参数提供类型值。我们可以想象委托不仅具有自由值参数,而且还具有自由类型参数

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# 中使用免费的泛型类型参数来模拟委托?

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

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 使用类型参数来选择正确的函数,将值参数传递给它并返回接收到的值。

最终解决方案: 在 C# 中使用免费的泛型类型参数模拟委托

4

3 回答 3

9

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

这是您的示例重新加工成一个有效的 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每次都可以使用不同的类型参数调用该方法。

于 2012-10-14T03:40:48.290 回答
2

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

我看不出这与我要求的通用方法委托之间有很大的语义差异。该解决方案非常类似于编译器为 lambdas [仅调用其他方法] 所做的(使用调用的方法创建闭包)。

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

  • 匿名函数/lambdas。我们无法创建通用 lambda。能够创建匿名类(如在 Java 中)将解决问题。但这并不是什么大问题,因为 lambda 只是 .Net 中的语法糖。

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

  • 定义通用委托的能力受到阻碍。我们不能IFactory<U, V>用 method 制作通用接口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
    }
}
于 2012-10-17T04:09:38.790 回答
2

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

你所描述的是一个类型构造函数——一个“函数”,它接受一个或多个类型并返回一个具体的(参数化封闭的)类型。

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

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


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

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

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

如果您想引用对该参数有约束的开放泛型方法,则该TPlaceholder参数是必需的;您可以选择满足约束的占位符类型。

于 2012-10-14T02:53:58.050 回答