.net 如何使for循环成为泛型?[副本]

wr98u20j  于 2023-06-07  发布在  .NET
关注(0)|答案(2)|浏览(361)

此问题已在此处有答案

Is there a constraint that restricts my generic method to numeric types?(24回答)
4年前关闭。
这篇文章是编辑并提交审查16小时前.
一个for循环

for (T i = a; i < b; i++)
{
    // do something
}

看起来很正常对吧
再看一眼,您会注意到T而不是原始数据类型-泛型类型。这不应该是一个问题,你会想。T可以是任何值, 不限于 一个数字(一个字符串,甚至一个复杂的对象),只要它是可比较和可枚举的(即您需要将约束where T: IEnumerable, IComparable应用于它)。
因为它不限于数字,而且Eric给出的答案适用于任何类型的对象我不相信这个问题是"Is there a constraint that restricts my generic method to numeric types?"问题的重复
但回到这个问题:我也是这么想的,然后我开始尝试写一些类似的东西

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

实现一个数字生成器,可用于double,int,…这将是一个通用的for循环。约束IEnumerableIComparable只是告诉编译器,这种类型的元素可以被枚举和比较(因为for循环中的i < b表达式和增量),如果C#有这样的约束,INumeric也会是一个非常有用的约束(当然,它会隐含地是可比较和可枚举的)。
令我惊讶的是,这个例子无法编译,它生成了以下错误:
CS 0019:运算符“<”不能应用于类型为“T”和“T”的操作数
CS 0023:运算符“++”不能应用于类型为“T”的操作数
原因似乎是数字没有约束,但似乎没有任何实际的解决方案,正如一些答案here所解释的那样。

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

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

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

IEnumerable<string> MyFor(string a, string b)
{
    for (string i = a; i.Length < b.Length; i = i + a)
    {
        yield return i;
    }
}

如果像上面所示的那样重载了它,则可以像下面这样调用它:

var intNumbers = MyFor((int)1, 10);
var doubleNumbers = MyFor((double)1, 10);
var stringFor = MyFor("*", "abcde");

并且由于参数的签名而选择正确的版本。当然,这并不优雅,因为您只是为了使用不同的数据类型而复制代码。
本质上,为了使代码更优雅,更少冗余,问题是:

1.有没有可能写一个泛型函数,就像那样调用

var intNumbers = MyFor<int>(1, 10);
var doubleNumbers = MyFor<double>(1, 10);
var stringFor = MyFor<string>("*", "abcde");

如第一个例子所示。

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

2. a)如何编写一个泛型约束,允许我递增和比较T类型的变量?
2. B)如果没有这样的约束,有没有一种方法可以用泛型参数模拟for循环?
3.如何使for循环成为泛型?

3npbholx

3npbholx1#

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

s3fp2yjn2#

  • 下面的答案是对以前在这个问题下的评论的总结。感谢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 = MyFor<int>(1, 5, x => { x++; return x; });
    public static IEnumerable<T> MyFor<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> MyFor<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:
        MyFor<double>(1, 5).Dump();
        
        // explicit increment (+1), example 1:
        MyFor<int>(1, 5, x => {x++; return x;}).Dump();
        
        // explicit increment (+0.75), example 2:
        MyFor<float>((float)1.5, (float)7.5, x => { x += (float)0.75; return x; }).Dump();

    }
}

上面的例子是用intdoublefloat调用Numbers,第三个例子使用了0.75的增量。

相关问题