Ответ 1
new public IEnumerator GetEnumerator()
{
using(IEnumerator ie = base.GetEnumerator())
while (ie.MoveNext()) {
yield return Path.Combine(baseDirectory, ie.Current);
}
}
Я пытаюсь реализовать 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;
}
}
new public IEnumerator GetEnumerator()
{
using(IEnumerator ie = base.GetEnumerator())
while (ie.MoveNext()) {
yield return Path.Combine(baseDirectory, ie.Current);
}
}
Если у вас есть С# 3, вам не нужно писать специальный класс для этого. Предположим, что у вас есть последовательность строк, например List<string>
или string[]
, все, что поддерживает IEnumerable<string>
, называемое filePathCollection
, вы можете просто использовать:
var prefixedPaths = filePathCollection.Select(path => baseDirectory + path);
Hey presto - у вас теперь есть IEnumerable<string>
путей с префиксом baseDirectory
, поэтому вы можете использовать foreach
на нем и т.д. Мы закончили.
Остальная часть этого ответа является более общим объяснением, которое поможет вам (и другим) определить место, где это может быть применено в других случаях.
Select
по существу означает: возьмите эту последовательность элементов, сделайте что-нибудь с каждым элементом и верните мне новую последовательность, содержащую все результаты. "Что-то" указывается путем предоставления метода, который принимает один параметр того же типа, который хранится в старой последовательности, и возвращает любой тип, который вам нравится, который будет типом элемента новой последовательности. В этом случае мы не меняем тип элемента. Также мы определяем метод "на месте" с помощью лямбда:
path => baseDirectory + path
Компилятор показывает, что тип элемента исходной коллекции string
, поэтому path
является string
- вы можете думать о path
как о той же роли, что и "переменная цикла", если вы пришлось написать все это самостоятельно. А на path
мы используем конкатенацию, поэтому результатом является другой string
, поэтому новая последовательность также должна быть IEnumerable<string>
. Это "вывод типа" и является важной частью того, как этот материал уменьшает количество кода, который вы должны написать.
Другое важное событие - это "закрытие", которое является техническим названием того, как мы заставляем нашу лямбду зависеть не только от ее "открытого" параметра path
, но и от "закрытого" параметра baseDirectory
, который isn ' t, даже будучи явно переданным в него в качестве параметра. Лямбда может просто достигать вне себя и добираться до переменных, видимых методом, который он определяет внутри. Это именно то, что освобождает вас от необходимости писать конструктор, который принимает baseDirectory
в качестве параметра и сохраняет его в поле _baseDirectory
, чтобы вы могли использовать его позже несколько раз каким-либо другим способом.
Обратите внимание, что новая последовательность всегда будет такой же длины, как и входящая последовательность. Если вы хотите отфильтровать элементы, используйте Where
. Если вы хотите сделать последовательность более длинной, используйте SelectMany
.
использование нового ключевого слова может вызвать проблемы полиморфизма: в случае, если некоторые из них:
List<string> files = new FilePathCollection();
вызов foreach (var files in files)
приведет к вызову неперечисленного перечислителя.
Я думаю, что лучшее наследует от IEnumerable<string>
и удерживает личное поле с вашим списком.
Например, это может быть способ сделать это: наследование от я 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
}
Вероятно, вы можете использовать base.GetEnumerator() и вручную перебрать его.
Однако, я думаю, вы столкнулись со всеми проблемами, связанными с созданием класса, как вы это делаете. Вы должны получить те же значения из списка, который вы им вложили. Например, с кодом, который вы показываете, вы получите разные значения, перечисляющие список, чем при использовании индексатора. Также было бы ясно, какие другие методы List делают, например Add() или Contains()?
Есть ли причина, по которой вы не можете просто создать статический метод, который принимает список имен файлов и базовый каталог, а затем генерирует новый список результатов, где каждый элемент является файлом dir +?
Вы можете просто применить к базовому типу.
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();
}