如何扩展Collection以提供过滤的对象集合?

时间:2013-12-24 19:28:38

标签: c# collections

我正在尝试创建一个与所有其他集合一样的集合,除非您从中读取它,它可以通过设置标记进行过滤。

考虑这个(公认的做作)的例子:

var list = new FilteredStringList() {
    "Annie",
    "Amy",
    "Angela",
    "Mary"
};
list.Filter = true;

我添加了四个项目,但后来我设置了“过滤器”标志,这样每当我读出任何内容(或做任何涉及读出它的内容)时,我都希望将列表过滤为以“A”。

这是我的班级:

public class FilteredStringList : Collection<string>
{
    public bool Filter { get; set; }

    protected new IList<string> Items
    {
        get
        {
            if(Filter)
            {
                return base.Items.Where(i => i.StartsWith("A")).ToList();
            }
            return base.Items;
        }
    }

    public IEnumerator GetEnumerator()
    {
        foreach(var item in Items)
        {
            yield return item;
        }
    }
}

我的理论是,我正在覆盖和过滤基础Collection对象中的Items属性。我假设所有其他方法都会从这个集合中读取。

如果我通过ForEach迭代,我得到:

Annie
Amy
Angela

耶!但是,如果我这样做:

list.Count()

我得到“4”。所以,Count()不尊重我的过滤器。

如果我这样做:

list.Where(i => i[1] == 'a')

我仍然得到“玛丽”回来,即使她不应该在那里。

我知道我可以覆盖各种各样的Collection方法,但我真的不想全部覆盖它们。另外,我希望所有LINQ方法都尊重我的过滤器。

4 个答案:

答案 0 :(得分:1)

听起来你的概念是有一个列表可以用两种不同的方式表示。这真的是两个列表,我建议将它作为两个列表运行。进一步提出你的做法:

// the base list containing all items
var list = new List<string>
{
    "Annie", "Amy", "Angela", "Mary"
};

然后,创建一个只有过滤项目的列表表达式:

 // do NOT .ToList() this list:
 var filteredList = list.Where (x => x.StartsWith("A"));

您可以继续添加/删除list中的项目,filteredList在您访问时始终会包含更新的值。

如果你真的必须只访问一个字段/属性,那么你可以将listfilteredList放入一个类中,该类还包含一个IsFiltered属性,用于确定哪个属性这两个列表要返回。

您要做的是创建一个返回正确列表的类。类似的东西:

public class Filtered<T>
{
    private IEnumerable<T> _list;
    private IEnumerable<T> _filteredList;

    public bool IsFiltered { get; set; }

    public IEnumerable<T> MyCollection { get { return IsFiltered ? _filteredList 
                                                                 : _list; }}

    public Filtered(IEnumerable<T> list, IEnumerable<T> filteredList)
    {
        _list = list;
        _filteredList = filteredList;
    }
}

而不是像原始示例那样绑定到list,而是绑定到上述类的MyCollection属性(filtered.MyCollection);

答案 1 :(得分:0)

不要继承Collection。而只是直接从Object继承,让方法实现所需的任何接口(可能是IList),创建一个内部List实例作为支持集合,然后将每个操作公开为你希望它被看到。

public class FilteredCollection : IList<string>, IEnumerable<string>
{
    private List<string> list = new List<string>();
    public bool Filter { get; set; }

    protected IList<string> Items
    {
        get
        {
            if (Filter)
            {
                return list.Where(i => i.StartsWith("A")).ToList();
            }
            return list;
        }
    }

    public IEnumerator<string> GetEnumerator()
    {
        return Items.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public int IndexOf(string item)
    {
        throw new NotImplementedException();
    }

    public void Insert(int index, string item)
    {
        throw new NotImplementedException();
    }

    public void RemoveAt(int index)
    {
        throw new NotImplementedException();
    }

    public string this[int index]
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    //continue with the rest
}

这里有几个优点。

  1. 您可以清楚地了解每种方法的用途。您不必担心让方法留下不合理的默认值。事实上,只有少数几个操作可能只是简单地调用List的相同方法,这是一个激励因素。为了真正代表一个过滤后的列表,几乎每个可能应该更改的操作,所以这并不像你为自己添加一堆工作。

  2. Collection的方法不是虚拟的,它们是密封的,这意味着将类类型作为基类将导致使用基类方法。这意味着当您认为已启用时,呼叫者可以绕过您添加的过滤器。这听起来真是个坏主意。

  3.   

    我的理论是,我正在覆盖和过滤基础Collection对象中的Items属性。我假设所有其他方法都会从这个集合中读取。

    但是你覆盖Items,你隐藏它。您甚至需要使用new关键字而不是override关键字来表明您没有覆盖它。即使您愿意,也无法使用override关键字,因为Collection中的所有方法/属性都是sealed,而不是virtual。它专门设计为,以防止您做您正在尝试做的事情。 (这就是为什么我通常认为它作为一般课程很少使用。)

      

    我知道我可以覆盖各种各样的Collection方法,但我真的不想全部覆盖它们。

    从技术上讲,您必须覆盖的唯一虚拟方法是ClearItemsInsertItemRemoveItemSetItem(以及Equals,来自GetHashCode的{​​{1}}和ToString。其余的方法/属性不是虚拟的,因此无法覆盖。唯一真正有效的解决方案是全力以赴。

答案 2 :(得分:0)

为什么不在需要时使用普通的List并应用Where子句?

var list = new List<string>() {
    "Annie",
    "Amy",
    "Angela",
    "Mary"
};

list.Where(o=>o.StartsWith("A")).Count();

答案 3 :(得分:-3)

可能会覆盖Add方法吗?这样你就可以覆盖所有基地。

public class FilteredStringList : Collection<string>
{
    public new void Add(string item)
    {
        if (item.StartsWith("A", StringComparison.InvariantCultureIgnoreCase))
            base.Add(item);
    }
}
  

我的理论是,我正在覆盖和过滤基础Collection对象中的Items属性。我假设所有其他方法都会从这个集合中读取。

请注意,这是对new修饰符隐藏方式的误解。使用protected new IList<string> Items隐藏属性时,并不意味着基本Items属性现在是虚拟的。对FilteredStringList的引用将使用新方法,但对Collection<string>的引用(包括基类中的所有方法)将继续使用“隐藏”基本方法。

修改每条评论

要拥有可以打开/关闭过滤器的集合,请考虑实现其中一个接口,如ICollection<T>。这样你可以应用你喜欢的任何过滤逻辑(即过滤添加,过滤检索等)。例如,如果过滤器应该在写入时应用,那么您将实现自定义Add,而GetEnumerator方法只是一个传递。如果过滤器应该在读取时应用,反之亦然:Add将是一个传递,枚举器方法将实现过滤器:

public class FilteredStringList<T> : ICollection<T>
{
    private Collection<T> _internalCollection = new Collection<T>();
    public bool IsFilterActive { get; set; }
    public Predicate<T> Filter { get; set; }

    public void Add(T item)
    {
        _internalCollection.Add(item);
    }

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

    public int Count
    {
        get { return _internalCollection.Where(item => !IsFilterActive || Filter(item)).Count(); }
    }

    public IEnumerator<T> GetEnumerator()
    {
        foreach (var item in _internalCollection)
        {
            if (!IsFilterActive || Filter(item))
                yield return item;
        }
    }

    // TODO implement other pass-through ICollection<T> methods
}