Реализация GetEnumerator () для коллекции, унаследованной от List <string> - PullRequest
15 голосов
/ 21 марта 2010

Я пытаюсь реализовать FilePathCollection.Его элементами будут простые имена файлов (без пути - например, «image.jpg»).Как только коллекция используется в цикле foreach, она должна вернуть полный путь, созданный путем объединения с baseDirectory.Как я могу это сделать?

public class FilePathCollection : List<string>
{
    string baseDirectory;

    public FilePathCollection(string baseDirectory)
    {
        this.baseDirectory = baseDirectory;
    }

    new public System.Collections.IEnumerator GetEnumerator()
    {
        foreach (string value in this._items) //this does not work because _list is private
            yield return baseDirectory + value;
    }
}

Ответы [ 5 ]

26 голосов
/ 21 марта 2010
new public IEnumerator GetEnumerator()
{
  using(IEnumerator ie = base.GetEnumerator())
    while (ie.MoveNext()) {
      yield return Path.Combine(baseDirectory, ie.Current);
  }
}
12 голосов
/ 21 марта 2010

Если у вас есть C # 3, вам не нужно писать специальный класс для этого.Предположим, у вас есть последовательность строк, такая как List<string> или string[], что-либо, поддерживающее IEnumerable<string>, называемое filePathCollection, вы можете просто использовать:

var prefixedPaths = filePathCollection.Select(path => baseDirectory + path);

Привет, теперь выиметь IEnumerable<string> путей с префиксом baseDirectory, так что вы можете использовать foreach на нем и т. д. Мы закончили.


Остальная часть этого ответа - более общее объяснение, чтобы помочьВы (и другие) замечаете, где это может быть применено в других случаях.

Select по существу означает: взять эту последовательность предметов, сделать что-то с каждым предметом и вернуть мне новую последовательность, содержащую все результаты.«Что-то» задается путем предоставления метода, который принимает один параметр того же типа, который хранится в старой последовательности, и возвращает любой тип, который вам нравится, который будет типом элемента новой последовательности.В этом случае мы не меняем тип элемента.Также мы определяем метод «на месте», используя лямбду:

path => baseDirectory + path

Компилятор выясняет, что тип элемента исходной коллекции равен string, поэтому path представляет собой string- вы можете думать, что path играет ту же роль, что и «переменная цикла», если вы должны были написать все сами.И в path мы используем конкатенацию, поэтому в результате получается еще один string, поэтому новая последовательность также должна быть IEnumerable<string>.Это «вывод типа» и важная часть того, как этот материал уменьшает объем кода, который вы должны написать.

Другая важная вещь - это «замыкание», которое является техническим названием того, как мы делаемнаша лямбда зависит не только от ее «открытого» параметра path, но также и от «закрытого» параметра baseDirectory, который даже не передается явно в качестве параметра.Лямбда может просто выйти за пределы себя и добраться до переменных, видимых методом, в котором она определена.Это именно то, что освобождает вас от необходимости писать конструктор, который принимает baseDirectory в качестве параметра и сохраняет его в поле _baseDirectory, чтобы вы могли использовать его позднее несколько раз в другом методе.

Обратите внимание, чтоновая последовательность всегда будет той же длины, что и входящая последовательность.Если вы хотите отфильтровать элементы, используйте Where.Если вы хотите сделать последовательность длиннее, используйте SelectMany.

4 голосов
/ 21 марта 2010

использование нового ключевого слова может вызвать проблемы с полиморфизмом: в случае, если некоторые из них:

List<string> files = new FilePathCollection();

вызов foreach (var files in files) вызовет не переопределенный перечислитель.

Я думаю, что лучше всего наследовать от IEnumerable<string> и держать личное поле со своим списком.

Например, это может быть способ сделать это: наследование от I List<string>, которое оно уже наследует от IEnumerable<T>

 public class FilePathCollection :  IList<string>
    {
        string baseDirectory;
        private List<string> internalList;

        public FilePathCollection(string baseDirectory)
        {
            this.baseDirectory = baseDirectory;
        }

        #region IList<string> Members

        public int IndexOf(string item)
        {
            return GetFileNameOnly(internalList.IndexOf(item));
        }
        private string GetFileNameOnly(string p)
        {
            //your implementation.......
            throw new NotImplementedException();
        }

        private int GetFileNameOnly(int p)
        {
           //your implementation.......
            throw new NotImplementedException();
        }

        public void Insert(int index, string item)
        {
            internalList.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            internalList.RemoveAt(index);
        }

        public string this[int index]
        {
            get
            {
                return GetFileNameOnly(internalList[index]);
            }
            set
            {
                this[index] = value;
            }
        }



        #endregion

        #region ICollection<string> Members

        public void Add(string item)
        {
            internalList.Add(item);
        }

        public void Clear()
        {
            internalList.Clear();
        }

        public bool Contains(string item)
        {
            return internalList.Contains(item);
        }

        public void CopyTo(string[] array, int arrayIndex)
        {
            internalList.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get { return internalList.Count; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(string item)
        {
            return internalList.Remove(item);
        }

        #endregion

        #region IEnumerable<string> Members

        public IEnumerator<string> GetEnumerator()
        {
            foreach(string value in internalList)
                yield return baseDirectory + value;
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            foreach(string value in internalList) 
                yield return baseDirectory + value;
        }

        #endregion
    }
1 голос
/ 21 марта 2010

Возможно, вы могли бы использовать base.GetEnumerator () и вручную выполнить итерацию.

Тем не менее, я думаю, что вы столкнетесь с различными проблемами при разработке класса так, как вы пытаетесь это сделать. Вы должны получить те же значения из списка, который вы добавили в них. Например, с кодом, который вы показываете, вы получите другие значения, перечисляющие список, чем при использовании индексатора. Кроме того, будет ли понятно, что другие методы List делают, например, Add () или Contains ()?

Есть ли причина, по которой вы не можете просто создать статический метод, который принимает список имен файлов и базовый каталог, а затем генерирует новый список результатов, где каждый элемент - dir + file?

0 голосов
/ 10 июня 2014

Вы можете просто привести к базовому типу.

new public System.Collections.IEnumerator GetEnumerator()
{
    foreach (string value in (List<string>)this) //<-- note the cast
        yield return baseDirectory + value;
}

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

public new IEnumerator<string> GetEnumerator()
{
    return ((List<string>)this).Select(x => baseDirectory + x).GetEnumerator();
}
...