Реализация GetEnumerator () для набора, наследованного от Списка <строка>

Я пытаюсь реализовать 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;
    }
}
15
задан John Willemse 3 May 2013 в 12:35
поделиться

4 ответа

new public IEnumerator GetEnumerator()
{
  using(IEnumerator ie = base.GetEnumerator())
    while (ie.MoveNext()) {
      yield return Path.Combine(baseDirectory, ie.Current);
  }
}
26
ответ дан 1 December 2019 в 00:45
поделиться

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

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

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

1
ответ дан 1 December 2019 в 00:45
поделиться

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

List<string> files = new FilePathCollection();

вызов foreach (var files in files) приведет к вызову не переопределенного перечислителя.

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

Например, можно сделать так: наследоваться от IList, который уже наследуется от IEnumerable

 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
    }
4
ответ дан 1 December 2019 в 00:45
поделиться

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

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

Hey presto - теперь у вас есть IEnumerable путей с префиксом baseDirectory, поэтому вы можете использовать foreach на нем и т.д. Мы закончили.


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

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

path => baseDirectory + path

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

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

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

12
ответ дан 1 December 2019 в 00:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: