【问题标题】:C# Extension MethodsC# 扩展方法
【发布时间】:2011-08-18 18:56:50
【问题描述】:

我目前正在尝试编写一个扩展方法,但它似乎没有按预期运行。在我们深入研究之前,这是我的代码:

 public static void Remove<T>(this IEnumerable<T> source, Func<T, bool> predicate)
 {
     var items = source.Where(predicate);

     source = source.Where(t => !items.Contains(t));
 }

希望我可以在任何 IEnumerable 上调用此扩展方法,然后从集合中删除与谓词匹配的所有项目。我厌倦了遍历集合以找到匹配的项目,然后一次删除一个,以避免在枚举时更改集合...

无论如何...当我单步执行代码时,似乎一切正常。在存在该方法之前,source 已删除正确数量的项目。但是,当我返回调用代码时,所有项目仍然存在于我原来的 IEnumerable 对象中。有什么建议吗?

提前致谢,
桑尼

【问题讨论】:

  • 还有一件事... Contains 方法是我编写的另一种扩展方法(按预期工作)。
  • 顺便说一句,不知道你是否知道,但 LINQ 确实有一个 Contains 方法:msdn.microsoft.com/en-us/library/bb357185.aspx

标签: c# .net lambda extension-methods ienumerable


【解决方案1】:

不能按照您最初编写的方式这样做,您正在获取一个引用变量 (source) 并使其引用一个新实例。这会修改本地引用 source 而不是传入的原始参数。

请记住,对于 C# 中的引用类型,默认的参数传递方案是按值传递(传递的值是引用)。

假设您将变量x 传递给此方法,它引用原始列表并且该列表位于理论位置1000,这意味着源是对位于位置1000 的原始列表的新引用。

现在当你说:

source = source.Where(....);

您正在将 source 分配给一个新列表(例如位置 2000),但这只会影响 source 指向的内容,而不影响您传入的 x

要将此作为扩展方法进行修复,您真的需要return 新序列:

 public static IEnumerable<T> Remove<T>(this IEnumerable<T> source, Func<T, bool> predicate)
 {
     if (source == null) throw new ArgumentNullException("source");
     if (predicate == null) throw new ArgumentNullException("predicate");

     // you can also collapse your logic to returning the opposite result of your predicate
     return source.Where(x => !predicate(x));
 }

这一切都假设您希望像您在问题中提出的那样使其完全通用 IEnumerable&lt;T&gt;。显然,正如在其他示例中所指出的那样,如果您只关心 List&lt;T&gt;,则有一个内置的 RemoveAll() 方法。

【讨论】:

  • 事实上,我可能会将它命名为 WhereNot() 而不是 Remove(),因为 Remove() 意味着它会改变原始列表,而 WhereNot() 与 Where() 有很好的平行声音。 ..
  • 关于命名的要点。我认为这种方法在当前形式下是多余的,因为现在它只是一个 Where() 包装器(带有免费的 !!)
  • 是的,但是如果您只想将方法组作为谓词而不是 lambda 来否定方法组到 Where(),它可能会很有用。这是相当微不足道的,当然,但我可以看到有人想说:WhereNot(MyPredicateMethod) 而不是 Where(x =&gt; !MyPredicateMethod(x))
  • 有趣的地方。我个人尝试对我的 LINQ 表达式进行 lambdafy,但这可以解决这个需求。
  • 哈,我喜欢“lambdafy”这个词,你应该 TM :-)
【解决方案2】:

这种扩展应该通过返回一个新的序列来实现。这样您就可以集成到一系列序列操作中:

public static IEnumerable<T> Remove<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
    return source.Where(t => !predicate(t));
}

var query = mySequence.Select(x => x.Y).Remove(x => x == 2).Select(x => 2*x);

现在该方法只不过是Where() 的包装,这显然没有帮助。你可以考虑去掉它。

如果您想实际更新底层集合(假设它甚至存在),那么您不能这样做,因为IEnumerable&lt;T&gt; 没有提供任何更改其内容的方法。您必须执行以下操作:

var myNewList = new List<int>(oldList.Remove(x => x == 2));

最后,如果您正在使用List&lt;T&gt;,您可以使用RemoveAll() 方法实际从列表中删除项目:

int numberOfItemsRemoved = myList.RemoveAll(x => x == 2);

【讨论】:

    【解决方案3】:

    试试这个,我认为这是一个有用的 List.RemoveAll(Predicate match) 方法:http://msdn.microsoft.com/en-us/library/wdka673a.aspx

    所以只需在您拥有的列表中使用它即可。

    source.RemoveAll(t => !items.Contains(t))
    

    或者您的扩展方法返回您所需的可枚举,您可以使用它。

    【讨论】:

      【解决方案4】:

      这是因为 IEnumerable 是不可变的 您必须从您的 Remove 方法中返回另一个序列才能使其工作:

      public static IEnumerable<T> Remove<T>(this IEnumerable<T> source, Func<T, bool> predicate)
      {
         var items = source.Where(predicate);
      
         return source.Where(t => !items.Contains(t));
      }
      

      【讨论】:

        猜你喜欢
        • 2010-09-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-18
        • 2014-05-13
        • 2016-10-30
        • 2020-04-15
        • 2010-12-03
        相关资源
        最近更新 更多