目前,要使用以下代码替换项目:
var oldItem = myList.Single(x => x.Id == newItem.Id);
var pos = myList.ToList().IndexOf(oldItem);
myList.Remove(oldItem);
myList.ToList().Insert(pos, newItem);
所以,我创建了一个扩展来执行与上面相同但使用lambda表达式,我想出了这个:
public static ICollection<T> Replace<T>(this IEnumerable<T> list, T oldValue, T newValue) where T : class
{
if (list == null)
throw new ArgumentNullException(nameof(list));
return list.Select(x => list.ToList().IndexOf(oldValue) != -1 ? newValue : x).ToList();
}
所以我可以按如下方式使用它:
var oldItem = myList.Single(x => x.Id == newItem.Id);
myList = myList.Replace(oldItem, newItem);
然而,它不起作用。我错过了什么?
答案 0 :(得分:2)
这就是你可能会这样做的方式(但实际上这是因为我们将要达到的原因,这是一种无害的浪费)。
您不希望承诺将任意枚举转换为List<T>
,因为IEnumerable<T>
的语义是它只需要按要求提供项目。它可能已准备好生成无限数量的项目,或者它可能在某个列表中有一百万个 - 但您的调用者可能只想跳过前五个并抓住接下来的三个。在调用者明确询问其他999,999,992个项目之前,请不要提前运行并让他等待你的所有内容。
如果没有需要将整个混乱复制到List<T>
,请不要。而且没有必要。 Select()
不这样做。您使用Select()
无法做任何事情,通常可以使用yield return
。排序是一个例外。在你看之前,你不可能知道第一百万件物品是否应该先出现。
另外要记住的是LINQ非常强大,开箱即用。当你的扩展方法像这些一样简单的单行时,你可能不应该写它们,除了作为介绍练习。
public static class X
{
public static IEnumerable<T> Replace<T>(this IEnumerable<T> items,
T oldValue, T newValue) where T : class
{
// Six years of college, down the drain.
return items.Select(x => x == oldValue ? newValue : x);
}
// Int32, long, double, etc. But you need Cast<T>().
public static IEnumerable<IComparable> Replace(this IEnumerable<IComparable> items,
IComparable oldValue, IComparable newValue)
{
return items.Select(x => x.CompareTo(oldValue) == 0 ? newValue : x);
}
// This strikes me as a more flexible way to go about it, but we'll
// see below what it really gains us in practice.
public static IEnumerable<T> Replace<T>(this IEnumerable<T> items,
Func<T, bool> selector, Func<T, T> converter)
{
return items.Select(x => selector(x) ? converter(x) : x);
}
public static void Test()
{
var r = Enumerable.Range(0, 20);
// Is this code really bad enough to justify writing an extension method?
var q0 = r.Select(x => x == 4 ? 0 : x);
// Nah. You could write explicit overloads for every numeric type. But why?
var q1 = r.Cast<IComparable>().Replace(4, 0);
// Here's the lambda version.
var q2 = r.Replace(x => (x % 3) == 0, x => x * 9);
// But does it really improve on this? I don't see it, myself.
var q3 = r.Select(x => ((x % 3) == 0) ? x * 9 : x);
// Remember, none of the above code is actually executed until you start
// enumerating them. This next line is the first time any of the code
// in any of the extension methods actually gets executed.
var list = q3.ToList();
}
}
答案 1 :(得分:1)
虽然您无法替换物化集合中的项目,但可以替换从其他 IEnumerable<T>
处获得的项目某个位置。
您需要做的就是使用Select
上的IEnumerable<T>
扩展方法在Id
属性匹配时映射到新项目,并使用新的IEnumerable<T>
实例,像这样:
// Assuming newItem is already defined, has the value you want to yield
// and T is the type of newItem
IEnumerable<T> newEnumerable = myList.Select(i =>
i.Id == newItem.Id ? newItem : i
);
然后,当您遍历newEnumerable
时,当旧列表中的项Id
等于newItem.Id
时,它将产生newItem
。
答案 2 :(得分:0)
由于该接口专为迭代而设计,因此您将很难使用IEnumerable
执行此操作。但是,您可以使用IList
public static IList<T> Replace<T>(IList<T> list, T oldValue, T newValue) where T : class
{
var pos = list.IndexOf(oldValue);
list.Remove(oldValue);
list.Insert(pos, newValue);
return list;
}
测试代码为
static void Main(string[] args)
{
IList<Item> myList = new List<Item>() { new Item { Id = "123" }, new Item { Id = "abc" }, new Item { Id = "XYZ" } };
var newItem = new Item { Id = "abc" };
var oldItem = myList.Single(x => x.Id == newItem.Id);
myList = Replace(myList, oldItem, newItem);
}
明确定义Item
class Item
{
public string Id { get; set; }
public readonly Guid Guid = Guid.NewGuid();
}
由于您的示例代码显示了ToList()
的使用,因此可能创建一个新集合(而不是修改现有集合)。如果是这种情况,您可以按如下方式编写Replace()
方法
public static ICollection<T> Replace<T>(IEnumerable<T> list, T oldValue, T newValue) where T : class
{
var l = list.ToList();
var pos = l.IndexOf(oldValue);
l.Remove(oldValue);
l.Insert(pos, newValue);
return l;
}
答案 3 :(得分:0)
IEnumerable
用于遍历通用数据结构。将其视为只读访问权限。事实上,在枚举它的同时对IEnumerable
进行修改是一件很重要的事情。您的原始解决方案很好,除了最后一行。只需删除并插入即可。