如何使for循环通用?

时间:2018-06-26 13:39:54

标签: c# .net generics

我在玩泛型,并尝试编写类似的东西

IEnumerable<T> Numbers<T>(T a, T b)
where T: IEnumerable, IComparable
{
    for (T i = a; i < b; i++)
    {
        yield return i;
    }
}

实现一个数字生成器,该生成器可用于double,int,... 这将是一个通用的for循环。但是此示例未编译,它生成以下错误:

  

CS0019:运算符“ <”不能应用于类型“ T”和“ T”的操作数   
CS0023:运算符'++'不能应用于类型'T'的操作数

原因似乎是数字没有任何约束,但是似乎没有任何实际的解决方案,here的某些答案正在对此进行解释。

注意:它的类似(非通用)版本可以编译:

IEnumerable<double> Numbers(double a, double b)
{
    for (var i = a; i < b; i++)
    {
        yield return i;
    }
}

IEnumerable<int> Numbers(int a, int b)
{
    for (var i = a; i < b; i++)
    {
        yield return i;
    }
}

如果两者都有,您可以像调用它一样

var intNumbers = Numbers((int)1, 10);
var doubleNumbers = Numbers((double)1, 10);

,并且由于参数的签名,选择了正确的版本。


本质上,问题是:

1。是否可以将通用函数编写为可以像

那样调用的函数
var intNumbers = Numbers<int>(1, 10);
var doubleNumbers = Numbers<double>(1, 10);

如我的第一个示例所示?

(我不确定正确的约束,我认为where T: IEnumerable, IComparable会成功,因为您需要比较i < b,并且需要迭代到下一个更大的数字)。

2。 a)如何编写通用约束,使我可以递增和比较类型T的变量?

2。 b)如果没有这样的约束,有没有办法用通用参数来模拟for循环?

3。如何使for循环通用?

2 个答案:

答案 0 :(得分:4)

for循环包含四个部分:

  • 初始化当前状态
  • 测试当前状态,如果测试失败,则停止
  • 执行动作
  • 创建新的当前状态

我们将breakcontinue排除在外,因为它们使事情复杂化了。

您希望将操作限制为产生值。精细。然后,我们想要的是Aggregate的新版本,它产生一个值:

public static IEnumerable<R> MyFor<S, R>(
  S initial, 
  Func<S, bool> test, 
  Func<S, S> increment, 
  Func<S, R> select) 
{

    for (S current = initial; test(current); current = increment(current))
        yield return select(current);

}

我们完成了。现在,您只需提供必要的lambda,就可以进行任何for循环:

static IEnumerable<double> MakeDoubles() => 
  MyFor(0.0, x => x <= 10.0, x => x + 1.0, x => x);

答案 1 :(得分:0)

以下答案是对该问题下先前评论的讨论的总结。感谢MistyK找到解决问题的方法。


问题在于,C#中目前不存在可以以通用方式使用的“数字”约束。

因此,使用通用类型T的此类for循环不起作用,因为编译器不允许:for (T i = a; i < b; i++)

作为一种解决方法,我们必须使用while循环并将约束structIComparable一起使用。 yield关键字没有任何限制,可以按预期运行。

根据实现方式(即是否要灵活递增),有两个函数可以解决此问题。

考虑以下代码:

using System;
using System.Collections.Generic;

public class Program
{
    // Answer 1:
    // The downside is that you need to provide your increment 
    // func and you can pass any type implementing 
    // IComparable.Usage: var xd = Numbers<int>(1, 5, x => { x++; return x; });
    public static IEnumerable<T> Numbers<T>(T a, T b, Func<T, T> increment) 
    where T : struct, IComparable 
    { 
        var i = a; while (i.CompareTo(b) < 0) 
        { 
            yield return i; i = increment(i); 
        } 
    }

    // Answer 2:
    // Idea without passing increment function but can cause runtime error 
    // if increment is not implemented: 
    public static IEnumerable<T> Numbers<T>(T a, T b)
    where T : struct, IComparable
    {
        dynamic i = a; while (i.CompareTo(b) < 0)
        {
            yield return i; i++;
        }
    }

    public static void Main()
    {
        // implicit increment:
        Numbers<double>(1, 5).Dump();

        // explicit increment (+1), example 1:
        Numbers<int>(1, 5, x => {x++; return x;}).Dump();

        // explicit increment (+0.75), example 2:
        Numbers<float>((float)1.5, (float)7.5, x => { x += (float)0.75; return x; }).Dump();

    }
}

使用DotNetFiddle来执行

以上示例使用Numbersintdouble调用float,而第三个示例使用了0.75的增量。