【问题标题】:How to replace an item in an IEnumerable<T>?如何替换 IEnumerable<T> 中的项目?
【发布时间】:2017-03-01 14:14:38
【问题描述】:

目前,要替换我正在使用以下代码的项目:

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

但是,它不起作用。我错过了什么?

【问题讨论】:

  • IEnumerable 并不是真正为替换事物而设计的,并且从设计上讲,它不能确定您是否可以/应该多次迭代它们。
  • 我假设Id 是唯一的,考虑一个Dictionary,其中键是Id,值是item
  • @EdPlunkett 我认为这属于“它不起作用”。
  • @EdPlunkett 那么我想我们应该提出一个规范的问题,以便我们可以关闭与该问题重复的问题。
  • 您真的需要它来处理IEnumerable 不会IList 吗?

标签: c# linq generics lambda


【解决方案1】:

这就是你可以这样做的方式(但这实际上是一种无害的努力浪费,原因我们会讲到)。

您不想承诺将任意枚举转换为List&lt;T&gt;,因为IEnumerable&lt;T&gt; 的语义是它只需要按要求提供项目。它可能已经准备好生成无限数量的项目,或者它可能在某个地方的列表中有一百万个——但你的调用者可能只想跳过前五个并抓住接下来的三个。在来电者明确询问其他 999,999,992 件物品之前,不要跑到前面让他在你抚弄它们时等着。

如果没有需要将整个混乱复制到List&lt;T&gt;,请不要。而且没有必要。 Select() 不会那样做。用Select() 做不到的任何事情,通常都可以用yield return 做。排序是一个例外。在您查看之前,您无法知道第 100 万个项目是否应该排在第一位。

要记住的另一件事是 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();
    }
}

【讨论】:

    【解决方案2】:

    虽然您无法替换具体化集合本身中的项目,但您可以替换在某个位置从不同IEnumerable&lt;T&gt; 产生的项目。

    您所要做的就是在IEnumerable&lt;T&gt; 上使用Select 扩展方法在Id 属性匹配时映射到新项目,并使用新的IEnumerable&lt;T&gt; 实例,如下所示:

    // 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 时,当旧列表中的项目具有等于newItem.IdId 时,它将产生newItem

    【讨论】:

      【解决方案3】:

      IEnumerable 用于遍历通用数据结构。将其视为只读访问。事实上,在枚举IEnumerable 的同时对其进行修改是一个很大的 NO NO。您的原始解决方案很好,除了最后一行。只需执行删除和插入。

      【讨论】:

        【解决方案4】:

        您将很难使用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;
            }
        

        【讨论】:

        • 这实际上不会替换该项目,而是删除旧项目并在末尾添加新项目。
        • @Magnus 当它不只是崩溃时,因为输入不一定是一个集合开始。
        • @Servy 确实,接受一个 IEnumerable 然后强制它是 ICollection 是没有意义的。在这种情况下,应该从一开始就接受 ICollection。
        猜你喜欢
        • 2015-06-24
        • 1970-01-01
        • 2011-03-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多