为什么C#中的foreach以这种方式运行,是否有一种方法可以在未来得到修复?

时间:2010-08-06 13:12:50

标签: c# .net type-conversion

long[] b = new long[1];
int i1 = b[0]; // compile error as it should

// no warning at all, large values gets converted to negative values silently
foreach (int i2 in b) 
{
}

class Customer : Person{}
Person[]p = new Person[]{mypers};

// no warning at all, throws typecastexception at runtime
foreach (Customer c in p) 
{
}

我知道他们不能简单地修复它,因为它会破坏现有程序。

但是为什么他们不在c#编译器中创建兼容性选项,我可以打开类型安全的foreach块生成,至少对于我确定它有效的新程序或程序?

5 个答案:

答案 0 :(得分:17)

foreach (var c in p) 
{
}

很好地避免了这个问题,不是吗?

答案 1 :(得分:6)

foreach表现得很好。

foreach遍历集合时,它不一定知道将返回的类型(它可以通过一组接口实现,或者只是旧.NET的对象)包'风格系列)。

考虑这个例子:

var bag = new ArrayList();
bag.Add("Some string");
bag.Add(1);
bag.Add(2d);

foreach如何在这个实例中表现(集合有一个字符串,一个int和一个double)?您可以强制foreach迭代最兼容的类型,在这种情况下,它是Object:

foreach(object o in bag)
{
    // Do work here
}

有趣的是,使用var将完全符合:

foreach(var o in bag)
{
    // o is an object here
}

但是对象的成员不会真正允许你做任何有用的事情。

做任何有用的事情的唯一方法是依靠开发人员指定他们想要使用的确切类型,然后尝试强制转换为该类型。

答案 2 :(得分:4)

您可以自由地在类型安全的IEnumerable上创建扩展方法:

public static void Each(this IEnumerable @this, Action action)
{
    if (@this == null) throw new ArgumentNullException("this");
    if (action == null) throw new ArgumentNullException("action");

    foreach (TElement element in @this)
    {
        action(element);
    }
}

它与foreach循环略有不同,会产生额外的开销,但会确保类型安全。

答案 3 :(得分:1)

至于原因:

使用数组,编译器实际上将foreach语句转换为类似于以下内容的内容。

private static void OriginalCode(long[] elements)
{
    foreach (int element in elements)
    {
        Console.WriteLine(element);
    }
}

private static void TranslatedCode(long[] elements)
{
    int element;
    long[] tmp1 = elements;
    int tmp2 = 0;

    while (tmp2 < elements.Length)
    {
        // the cast avoids an error
        element = (int)elements[tmp2++];
        Console.WriteLine(element);
    }
}

如您所见,生成的强制转换避免了运行时错误,当然也会导致您的语义错误。

顺便说一下,IEnumerable和foreach也是如此,它转化为以下代码导致相同的行为和问题。

private static void OriginalCode(IEnumerable<long> elements)
{
    foreach (int element in elements)
    {
        Console.WriteLine(element);
    }
}

private static void TranslatedCode(IEnumerable<long> elements)
{
    int element;
    IEnumerator<long> tmp1 = elements.GetEnumerator();

    try
    {
        while (tmp1.MoveNext())
        {
            element = (int)tmp1.Current;
            Console.WriteLine(element);
        }
    }
    finally
    {
        (tmp1 as IDisposable).Dispose();
    }
}

答案 4 :(得分:0)

编辑:根据codymax的评论澄清并更正

根据我对C# Spec foreach中第8.8.4节的解释,扩展如下。 (参见开头的部分“如果表达式的X类型是数组类型,则存在从X到System.Collections.IEnumerable接口的隐式引用转换。”

第一种情况将int.MaxValue以上的值转换为-1。这是因为operations don't throw overflow by default 从第一种情况看,转换是在未经检查的上下文部分7.6.12描述了应该发生的是什么

  

结果因丢弃而被截断   任何不适合的高阶位   目的地类型。

第二个按预期执行抛出运行时InvalidCastException。这可能是因为Enumerator.Current返回的类型。可能有一节介绍了这一点,但我没有找到它。

        long[] b = long[0] ;


        System.Collections.IEnumerator le = ((long[])(b)).GetEnumerator();

        int i2;
        while (le.MoveNext())
        {
            i2 = (int)(long)le.Current;

        }




        Person[] p = new Person[1] { mypers };

        System.Collections.IEnumerator e = ((Person[])(p)).GetEnumerator();

        Customer c;
           while (e.MoveNext())
           {
               c = (Customer)(Person)e.Current;

            }
相关问题