【问题标题】:Emulating delegates with free generic type parameters in C#在 C# 中使用免费的泛型类型参数模拟委托
【发布时间】:2012-10-04 10:30:14
【问题描述】:

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

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

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

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

非泛型类中的

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

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

问题在于 .Net 对于具有未指定泛型类型参数的泛型函数没有等效的委托。以后不能为 type-parameters 提供 type-values。 我们可以想象委托不仅有免费的值参数,还有免费的类型参数

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

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

【问题讨论】:

  • 查看条款open type,尤其是type constructor
  • 我认为 C# 在不使用反射或使 Worker 通用的情况下没有任何方法可以做到这一点。我怀疑 .Net 不支持的高级类型会有所帮助。
  • 我不确定,但您是否看过 F#?强制 C# 变得更实用可能会很痛苦。

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


【解决方案1】:

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

这是您的示例重新加工成一个有效的 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 方法。

【讨论】:

  • 仅供参考,我认为您翻转了工厂方法调用。 ListCreationWorker 正在调用Factory.CreateSetSetCreationWorker 正在调用Factory.CreateList
  • @ChrisSinclair,是的,现在已修复。
  • 这么简单!!!我需要睡在这个上面,用新的头脑看看这个,以确保我没有弄错,这真的解决了我的问题!附言应该接口的不是 Worker(只有一个 Worker.DoWork 的实现和未定义数量的工厂),而是 Factory。但这并不能打败解决方案。
  • 我对您的解决方案进行了一些修改,为工厂而不是工人制作界面 (stackoverflow.com/a/12926979/1497385)。我看不出这与我要求的通用方法委托之间的语义差异。我们只是失去了一些语法糖。该解决方案非常类似于编译器为 lambdas [仅调用其他方法] 所做的(使用调用的方法创建闭包)。非常感谢。
  • 多年后,仍然相关,优雅和辉煌。确实比“开放”委托语法要冗长得多,但我明白为什么这不是 C# 团队的最高优先级功能。
【解决方案2】:

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

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

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

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


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

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

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

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

【讨论】:

  • 好吧,我可以使用开放类型Type openType = typeof(List&lt;&gt;)。这看起来像一个开放类型。我还可以从开放类型构造具体类型:Type closedType = typeof(Func&lt;,&gt;).MakeGenericType(typeof(int), typeof(string))
  • @Ark-kun:您可以使用反射表示开放类型,但不能作为类型系统中的类型。 Func&lt;&gt; 仅在 typeof() 中有效。同样,您只能将开放方法表示为MethodInfos。
  • 我正在尝试开箱即用。也许有一些逻辑技术或模式可以给我相同的行为(没有反射、动态代码、代码生成等)。
  • @Ark-kun:这取决于你实际想要完成的任务。你能展示一个示例用例吗?
  • Worker.DoWork&lt;T&gt; 是一个通用函数。它可以只做 'Factory.CreateList' 或 'Factory.CreateSet' 而没有任何反射。如果它知道使用哪一个就好了……(当然,潜在工厂的数量是无限的)
【解决方案3】:

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

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

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

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

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

  • 无法定义通用委托。我们不能做一个 通用IFactory&lt;U, V&gt; 接口与方法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
    }
}

【讨论】:

  • 太糟糕了,没有任何此类结构的语法糖,因为泛型接口可以允许方法接受演员对象及其适当的参数,而不必将参数包装在委托和/或闭包中.它们甚至可以传递ref 参数,而闭包则不能。
  • @supercat 通过闭包,你的意思是一个 lambda 表达式? lambda 确实支持 refout 参数。但是,您必须将其分配给您必须定义的适当委托。它们与内置的 ActionFunc 代表不兼容。
  • @mikez:给定delegate ActByRef&lt;T1&gt;(ref T1 p1),将void DoSomething(Action proc) 替换为void DoSomething&lt;TP1&gt;(IActByRef&lt;TP1&gt; proc, ref TP p1); 将允许希望使用局部变量执行操作的代码将委托与ref 一起传递给静态方法到包含这些变量的结构,避免需要创建一个堆对象来保存这些变量,另一个来保存一个绑定到它的新委托(对于被调用的方法不必保留委托和关联变量的情况)。这部分甚至适用于代表,但是......
  • ...虽然可以为采用proc 的方法创建一个委托,但不能为泛型方法创建一个委托。
猜你喜欢
  • 1970-01-01
  • 2023-01-26
  • 1970-01-01
  • 2012-12-05
  • 2021-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多