简体   繁体   English

为从List继承的集合实现GetEnumerator() <string>

[英]Implementing GetEnumerator() for a collection inherited from List<string>

I am trying to implement FilePathCollection . 我正在尝试实现FilePathCollection Its items would be simple file names (without a path - such as "image.jpg"). 它的项目是简单的文件名(没有路径 - 例如“image.jpg”)。 Once the collection is used via foreach cycle, it should return the full path created by concatenating with baseDirectory . 一旦通过foreach循环使用该集合,它应该返回通过与baseDirectory连接而创建的完整路径。 How can I do that? 我怎样才能做到这一点?

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);
  }
}

If you have C# 3, you don't need to write a special class to do this. 如果您有C#3,则无需编写特殊类来执行此操作。 Supposing you have a sequence of strings, such as a List<string> or string[] , anything that supports IEnumerable<string> , called filePathCollection , you can just use: 假设您有一系列字符串,例如List<string>string[] ,任何支持IEnumerable<string> ,称为filePathCollection ,您只需使用:

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

Hey presto - you now have an IEnumerable<string> of paths prefixed with baseDirectory , so you can use foreach on it, etc. We're done. 嘿presto - 你现在有一个以baseDirectory为前缀的IEnumerable<string>路径,所以你可以在它上面使用foreach等等。我们已经完成了。


The rest of this answer is more general explanation to help you (and others) spot where this can be applied in other cases. 本答案的其余部分是更一般的解释,以帮助您(和其他人)发现在其他情况下可以应用的地方。

Select essentially means: take this sequence of items, do something with each item and give me back a new sequence containing all the results. Select本质上意味着:采取这一系列的项目,对每个项目做一些事情并给我一个包含所有结果的新序列。 The "something" is specified by providing a method that accepts one parameter of the same type as stored in the old sequence, and returns any type you like, which will be the item type of the new sequence. 通过提供一个方法来指定“something”,该方法接受与旧序列中存储的相同类型的一个参数,并返回您喜欢的任何类型,这将是新序列的项类型。 In this case, we're not changing the item type. 在这种情况下,我们不会更改项目类型。 Also we're defining the method "in place" using a lambda: 我们还使用lambda定义了“就地”方法:

path => baseDirectory + path

The compiler figures out that the element type of the source collection is string , so path is a string - you can think of path as playing the same role as a "loop variable" if you had to write the whole thing out yourself. 编译器发现源集合的元素类型是string ,因此path是一个string - 如果你必须自己编写完整的东西,你可以认为path扮演与“循环变量”相同的角色。 And on path we use concatenation, so the result is another string , so the new sequence must also be IEnumerable<string> . path我们使用连接,因此结果是另一个string ,因此新序列也必须是IEnumerable<string> This is "type inference" and is an important part of how this stuff reduces the amount of code you have to write. 这是“类型推断”,是这些东西减少你必须编写的代码量的重要部分。

The other important thing happening is "closure", which is the technical name for how we make our lambda depend not only on its "open" parameter path but also on a "closed" parameter baseDirectory which isn't even being explicitly passed into it as a parameter. 发生的另一个重要事情是“闭包”,这是我们如何使lambda不仅依赖于它的“开放”参数path而且依赖于“封闭”参数baseDirectory的技术名称,该参数甚至没有明确地传递给它作为参数。 A lambda can just reach outside itself and get to the variables visible the method it is defined within. lambda可以直接到达外部并获得可见的变量。 This is specifically what frees you from the need to write a constructor that takes baseDirectory as a parameter and stores it in a _baseDirectory field so you can use it later repeatedly in some other method. 这使您无需编写构造函数,该构造函数将baseDirectory作为参数存储并存储在_baseDirectory字段中,以便稍后可以在其他方法中重复使用它。

Note that the new sequence will always be the same length as the incoming sequence. 请注意,新序列的长度始终与传入序列的长度相同。 If you want to filter items out, use Where . 如果要过滤项目,请使用Where If you want to make a sequence longer, use SelectMany . 如果要延长序列,请使用SelectMany

using the new keyword can cause you polymorphism problems: in case some does: 使用new关键字可能会导致多态问题:如果有的话:

List<string> files = new FilePathCollection();

calling foreach (var files in files) will cause to call the not overridden enumerator. 调用foreach (var files in files)将导致调用未重写的枚举器。

I think the best is inherit from IEnumerable<string> and hold a private field with your List. 我认为最好的是从IEnumerable<string>继承并保存一个包含List的私有字段。

For example this could be a way to do it: inheriting from I List<string> which it already inherit from IEnumerable<T> 例如,这可能是一种方法:继承自已从IEnumerable<T>继承的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
    }

You could probably use base.GetEnumerator() and manually iterate over that. 您可以使用base.GetEnumerator()并手动迭代它。

However, I think you've going to run in to all kinds of problems designing the class the way you're trying to do it. 但是,我认为你要按照你试图做的方式来解决设计课程的各种问题。 You should get the same values out of a list that you put in to them. 您应该从放入它们的列表中获取相同的值。 For example, with the code you show you'd get different values enumerating the list than when using the indexer. 例如,使用您显示的代码,您将获得枚举列表的不同值,而不是使用索引器时。 Also, would it be clear what other methods of List do such as Add() or Contains()? 是否会明确List的其他方法,例如Add()或Contains()?

Is there a reason you couldn't just create a static method that takes in a file name list and a base directory, then generates a new result list where each element is dir+file? 有没有理由你不能只创建一个接收文件名列表和基目录的静态方法,然后生成一个新的结果列表,其中每个元素都是dir + file?

You could just cast to base type. 你可以转换为基类型。

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

Ideally I would just use the Select extension method. 理想情况下,我只会使用Select扩展方法。 And give it a generic overload please... 请给它一个通用的过载请...

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM