从IEnumerable返回对值的引用

时间:2014-12-21 20:50:51

标签: c# reference ienumerable

我是c#的新手,所以问题可能有点愚蠢。

我想迭代收集并在适当位置更改它的值(返回对值的引用)。

代码我想看起来像这样:

IEnumerable<int> Iterate(int[] values)
{
    foreach (int value in values) { yield return value; }
}

void some() 
{
    int[] collection = new int[] {1,2,3};
    foreach (int elem in Iterate(collection)) {
        elem = elem*2;
    }
// collection is now a {2,4,6}

}

那么最简单的方法是什么?感谢。

UPD。真正的问题是难以理解的。这只是示例显示我想使用“引用整数”或smth。

我只想在C#中更接近这个C ++代码的类比:

int a[3] = {1, 2, 3};
for (auto& value: a) { value*=2; }

5 个答案:

答案 0 :(得分:2)

使用强大的Linq功能。

int[] collection = new int[] {1,2,3}
var modified = collection.Select(i => i * 2).ToArray();

您还可以使用一种方法(在c ++中称为函数)来隐藏更复杂的转换。

int Transform(int input)
{
    return input * 2;
}

void Later()
{
    collection.Select(Transform);
}

答案 1 :(得分:1)

你的问题有一个简单的答案:那是不可能的。

让我们来看看这个foreach (var item in enumerable)在幕后如何运作。

当您声明foreach (var item in enumerable) item.DoWork();时,.NET编译器会将其转换为:

var enumerator = enumerable.GetEnumerator();
try 
{
    while (enumerator.MoveNext()) 
    {
        var current = enumerator.Current;
        current.DoWork();
    }
}
finally 
{
    enumerator.Dispose();
}

这有一个重要的事情。只要集合保持不变,创建的枚举器仍然有效。如果对集合进行了更改,例如添加,修改或删除元素,则枚举数将无法恢复,并且下一次调用MoveNext()将引发InvalidOperationException

因此,您无法在通过IEnumerable循环获取foreach时对其进行修改。

答案 2 :(得分:0)

使用for:

for(int i=0;i<collection.Length;i++)
{
  collection[i]=collection[i]*2;
}

答案 3 :(得分:0)

C ++ for-auto-ref语法也吸引了我,因为(至少出于对值类型对象列表进行就地突变的目的)它比C#中的for-index循环更具表现力。但是,您仍然可以通过隐藏帐簿的“帮助程序”方法在安全的C#中获得类似的表达。例如:

static void For<T>(IList<T> a, Func<T, T> f)
{
    for (var i = 0; i < a.Count; ++i)
    {
        a[i] = f(a[i]);
    }
}

// Usage:
int[] a = { 1, 2, 3 }; // "a" can be any type that implements IList<T>
For(a, value => value * 2);

如果已测量这是代码中的热点,则可能需要为数组定义一个类似的重载,以允许编译器和/或运行时更好地进行优化。例如:

// About 65% faster than with IList<T> (according to a rough benchmark on .NET Framework 4.8)
static void For<T>(T[] a, ForBody<T> f)
{
    for (var i = 0; i < a.Length; ++i)
    {
        f(ref a[i]);
    }
}

delegate void ForBody<T>(ref T value);

// Usage:
int[] a = { 1, 2, 3 }; // "a" has to be an array
For(a, (ref int value) => value *= 2);

最后,如果在此方法的调用现场性能确实至关重要,则应使用实际的for语句替换此方法的使用,因为委托调用会增加不小的开销。在我的基准测试中,使用for语句代替此方法的速度提高了约30%。

  

此答案曾经有两个基于指针的选项。一个是使用C#7.3的unmanaged类型约束的泛型,另一个是非泛型的。与上述在安全代码中使用通用数组的方法(委托在ref T上进行操作)相比,前者的速度要慢5%,而后者的速度要慢1%。安全代码FTW!

答案 4 :(得分:0)

距离上次更新已经很长时间了。从 C# 7.3 开始,可以在 foreach 中使用 ref locals。结合属性 getter 的 ref 返回,它允许编写如下代码:

    void some() 
    {
        int[] collection = new int[] {1,2,3};
        foreach (ref int elem in collection.AsSpan()) 
            elem = elem*2;
    }

.AsSpan() 调用是必要的,因为 T[] 数组的默认枚举器确实将纯 T 作为 Current 属性返回类型。 Span 类型中的枚举器确实使用 ref T Current {get;} 代替,因此允许根据主题启动器的要求直接修改集合。