【问题标题】:How to replace an element in a Collection如何替换集合中的元素
【发布时间】:2018-08-24 13:39:50
【问题描述】:

我想做的事情看起来很简单——我想在ICollection<T> 中找到一个满足给定谓词的元素并将其替换为另一个。在 C++ 中,我会这样写:

for(auto &element : collection) {

    if(predicate(elem)) {
        element = newElement;
    }
}

通过引用抓取元素并重新分配它。然而做

foreach(ref var element in collection)

在 C# 中无法编译,我不确定如果它编译了它是否会做我想做的事。如何访问集合中的物理引用来修改它?

如果有帮助,我的方法签名:

public static void ReplaceReference<T>(
    ICollection<T> collection, 
    T newReference, 
    Func<T, bool> predicate)

编辑:

由于看起来不清楚,我不能只使用ICollection&lt;T&gt; 并将其更改为其他内容。我收到了ICollection - 这就是我所知道的,我无法改变它。无论我多么希望成为IListIEasilyReplacable,我都无法影响它。

【问题讨论】:

  • 我删除了 C++,因为这与编写 C++ 代码没有任何关系
  • 当您使用foreach 迭代集合时,您不能修改引用本身。但是,您可以修改引用项的成员。

标签: c# .net collections


【解决方案1】:

根据我的理解,您想根据给定的谓词替换集合中的特定项目,我尝试了下面的代码,它对我来说很好。

我创建了一个包含 4 个项目的字符串列表,我要求我的通用方法搜索值为“Name 1”的字符串,如果它是真的,它应该将其更改为值“Name 5”。

我已经使用控制台应用程序对其进行了测试,因此您可以通过创建使用 Console.WriteLine() 显示列表值的 forloop 来测试它;

    public void Main(string[] args)
    {
        List<string> list = new List<string>();
        list.Add("Name 1");
        list.Add("Name 2");
        list.Add("Name 3");
        list.Add("Name 4");

        Func<string, bool> logicFunc = (listItemValue) => listItemValue == "Name 1";
        ReplaceReference(list, "Name 5", logicFunc);
    }

    public static void ReplaceReference<T>(ICollection<T> collection, T newReference, Func<T, bool> predicate)
    {
        var typeName = typeof(T).Name;
        var newCollection = collection.ToList();
        for (int i = 0; i < newCollection.Count; i++)
        {
            if (predicate(newCollection[i]))
            {
                newCollection[i] = newReference;
            }
        }
    }

【讨论】:

【解决方案2】:
  • ICollection&lt;T&gt; 不是这种情况下的最佳选择。 IList&lt;T&gt; 允许您使用索引器进行分配。
  • 另一种选择是在迭代时创建一个新集合。
  • 您还可以编写某种包装器,它是集合中的实际引用并保存值:
ICollection<Wrapper<T>> collection = ...;

foreach(var wrapper in collection)
{
    wrapper.Value = newValue;
}

【讨论】:

  • .Remove(oldValue).Add(newValue)(与ICollection&lt;T&gt;
  • @RandRandom 您不应该在枚举时修改集合。大多数收藏品也会在末尾添加.Add
  • you shouldn't modify a collection while its enumerating - 你的第一点也是如此
  • @RandRandom 它只改变一个值,而不是集合的结构。
  • 如果你只这样做,它仍然会因枚举更改异常而中断
【解决方案3】:

所以我把头撞在墙上,想出了一个非常简单的解决方案来解决特定的替换问题,即查找、删除然后添加。

var existing = collection.FirstOrDefault(predicate);

if (existing != null)
{
    collection.Remove(existing);    
    collection.Add(newReference);
}

但是,我认为这是我的foreach 问题的解决方法,因此将此问题作为后续问题发布:Grab element from a Collection by reference in a foreach

编辑:

对于 Daniel A. White 的评论:

只处理第一个是我打算做的,但它可以很容易地更改为全部替换:

var existing = collection.Where(predicate);

foreach(var element in existing)
{
    collection.Remove(element);
}

for(int i = 0; i < existing.Count); ++i)
{
    collection.Add(newReference);
}

至于订购 - ICollection 不一定订购。所以解决这个问题的方法是创建一个签名不太通用的新方法

static void ReplaceReference<T>(
    IList<T> list, 
    T newReference, 
    Func<T, bool> predicate)

将使用索引器替换值

for(int i = 0; i < list.Count; ++i)
{
    if(predicate(list[i]))
    {
        list[i] = newReference;
        // break here if replace-one variant.
    }
}

现在在 main 方法中,我们检查我们的集合是否是 IList,因此是有序的,并将其传递给有序版本:

if(collection is IList<T> list)
{
    ReplaceReference(list, newReference, predicate);
    return;
}

================================================ ==============================

旁注:当然也有dumbo方式:

var newCollection = new List<T>();

foreach(var element in collection)
{
    newList.Add(predicate(element) ? newReference : element);
}

collection.Clear();

foreach(var newElement in newCollection)
{
    collection.Add(newElement);
}

但效率极低。

【讨论】:

  • 这仅占第一个。加上它很可能会放在集合的末尾。
  • 编辑了这些问题的解决方案。
猜你喜欢
  • 1970-01-01
  • 2015-11-19
  • 2019-12-02
  • 2022-10-15
  • 2015-08-11
  • 2021-09-16
  • 2012-01-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多