Методы расширения C # - PullRequest
       30

Методы расширения C #

2 голосов
/ 18 августа 2011

В настоящее время я пытаюсь написать метод расширения, но, похоже, он работает не так, как задумано. Прежде чем мы углубимся слишком глубоко, вот код, который у меня есть:

 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. Любые советы?

Заранее спасибо,
Sonny

Ответы [ 4 ]

11 голосов
/ 18 августа 2011

Не можете сделать так, как вы изначально написали, вы берете ссылочную переменную (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<T>, как вы задали в своем вопросе. Очевидно, как и в других примерах, если вам небезразлично List<T>, существует запеченный RemoveAll() метод.

4 голосов
/ 18 августа 2011

Это расширение должно быть реализовано с помощью , возвращающего новую последовательность. Таким образом, вы можете интегрироваться в цепочку последовательных операций:

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<T> не предоставляет никакого способа изменить ее содержимое. Вы должны сделать что-то вроде:

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

Наконец, если вы работаете с List<T>, вы можете использовать метод RemoveAll() для фактического удаления элементов из списка:

int numberOfItemsRemoved = myList.RemoveAll(x => x == 2);
1 голос
/ 18 августа 2011

попробуйте это, есть полезный метод List.RemoveAll (Predicate match), который, я думаю, предназначен для этого: http://msdn.microsoft.com/en-us/library/wdka673a.aspx

, поэтому просто используйте его в вашем списке.

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

или ваш метод расширения возвращает вам требуемое перечислимое значение, и вы можете использовать его.

0 голосов
/ 18 августа 2011

Это потому, что 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));
}
...