如何替换IEnumerable <t>中的项目?

时间:2017-03-01 14:14:38

标签: c# linq generics lambda

目前,要使用以下代码替换项目:

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

然而,它不起作用。我错过了什么?

4 个答案:

答案 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进行修改是一件很重要的事情。您的原始解决方案很好,除了最后一行。只需删除并插入即可。