我正在尝试实施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;
}
}
答案 0 :(得分:26)
new public IEnumerator GetEnumerator()
{
using(IEnumerator ie = base.GetEnumerator())
while (ie.MoveNext()) {
yield return Path.Combine(baseDirectory, ie.Current);
}
}
答案 1 :(得分:12)
如果您有C#3,则无需编写特殊类来执行此操作。假设您有一系列字符串,例如List<string>
或string[]
,支持IEnumerable<string>
的任何字符串,名为filePathCollection
,您只需使用:
var prefixedPaths = filePathCollection.Select(path => baseDirectory + path);
嘿presto - 你现在有IEnumerable<string>
个以baseDirectory
为前缀的路径,所以你可以使用foreach
等等。我们已经完成了。
这个答案的其余部分是更一般的解释,以帮助您(和其他人)发现在其他情况下可以应用的地方。
Select
本质上意味着:采取这一系列项目,对每个项目做一些事情并给我一个包含所有结果的新序列。通过提供一个方法来指定“something”,该方法接受与旧序列中存储的相同类型的一个参数,并返回您喜欢的任何类型,这将是新序列的项类型。在这种情况下,我们不会更改项目类型。我们还使用lambda“
path => baseDirectory + path
编译器发现源集合的元素类型为string
,因此path
为string
- 您可以将path
视为扮演相同的角色作为一个“循环变量”,如果你必须自己写出整个事情。在path
上我们使用连接,因此结果是另一个string
,因此新序列也必须是IEnumerable<string>
。这是“类型推断”,是这些东西减少你必须编写的代码量的重要部分。
另一个重要的事情是“闭包”,这是我们如何使lambda不仅依赖于其“开放”参数path
而且依赖于“封闭”参数{{1}的技术名称。这甚至没有作为参数显式传递给它。 lambda可以直接到达外部并获得可见的变量。这使得您无需编写构造函数,该构造函数将baseDirectory
作为参数存储并存储在baseDirectory
字段中,以便稍后可以在其他方法中重复使用它。
请注意,新序列的长度始终与传入序列的长度相同。如果要过滤项目,请使用_baseDirectory
。如果您想要延长序列,请使用Where
。
答案 2 :(得分:4)
使用new关键字会导致多态问题: 如果有人这样做:
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
}
答案 3 :(得分:1)
你可以使用base.GetEnumerator()并手动迭代它。
但是,我认为你会以你试图去做的方式遇到各种各样的问题。您应该从放入它们的列表中获取相同的值。例如,使用您显示的代码,您将获得枚举列表的不同值,而不是使用索引器时。是否清楚List的其他方法如Add()或Contains()?
有没有理由你不能创建一个接收文件名列表和基目录的静态方法,然后生成一个新的结果列表,其中每个元素都是dir + file?
答案 4 :(得分:0)
你可以转换为基类型。
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();
}