我在使用linq方面还很陌生,并且已经使用了几天了。我当前的问题是,我有一个可枚举的对象,并且该枚举中有一个特定的对象,应将其替换为新实例。
对于for循环,我可以这样实现:
public IEnumerable<Foo> ReplaceFirst(IEnumerable<Foo> enumerable, Foo obj)
{
Foo[] array = enumerable.ToArray();
bool used = false;
for (int i = 0; i < array.Length; i++)
if(array[i] == obj)
{
array[i] = new Foo();
used = true;
break;
}
if (used)
return array;
else
throw new ArgumentException("obj not found in enumerable");
}
现在我正在搜索linq的实现。到目前为止,我发现的是:
public IEnumerable<Foo> ReplaceFirst(IEnumerable<Foo> enumerable, Foo obj)
{
return enumerable.Contains(obj)
? locatedPieces.TakeWhile(current => current != obj)
.Append(new Foo())
.Concat(enumerable.SkipWhile(current => current != obj)
.Skip(1))
: throw new ArgumentException("obj not found in enumerable");
}
但是我不确定这个实现是否有效,因为它必须迭代至少3次(在 Contains , TakeWhile 和跳过)。
是否有更好的方法用linq实现此功能,还是应该继续使用for循环变量?
答案 0 :(得分:6)
我建议为此创建一个扩展方法,如下所示:
public static IEnumerable<T> Replace<T>(this IEnumerable<T> source, T oldVal, T newVal)
{
return source.Select(e => EqualityComparer.Default.Equals(e, oldVal) ? newVal : e);
}
这将循环遍历源并返回源元素,但那些== oldValue的元素除外。
这也使用了延迟执行。仅当您开始枚举结果IEnumerable时才枚举源。因此,如果您在调用此新的Replace扩展名后更改了源序列,则生成的序列也将产生此更改。
答案 1 :(得分:2)
如果找到匹配项,它将用新项目替换匹配项;如果找不到匹配项,则将其抛出。它不是很“ linqy”,但是它没有分配,并且只迭代一次。
private static IEnumerable<Foo> ReplaceFirst(IEnumerable<Foo> source, Foo target)
{
bool found = false;
foreach (var item in source)
{
if (found)
{
yield return item;
}
else if (EqualityComparer<Foo>.Default.Equals(item, target))
{
found = true;
yield return new Foo();
}
else
{
yield return item;
}
}
if (!found)
{
throw new InvalidOperationException("not found");
}
}