我正在尝试创建一个与所有其他集合一样的集合,除非您从中读取它,它可以通过设置标记进行过滤。
考虑这个(公认的做作)的例子:
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方法都尊重我的过滤器。
答案 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
在您访问时始终会包含更新的值。
如果你真的必须只访问一个字段/属性,那么你可以将list
和filteredList
放入一个类中,该类还包含一个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
}
这里有几个优点。
您可以清楚地了解每种方法的用途。您不必担心让方法留下不合理的默认值。事实上,只有少数几个操作可能只是简单地调用List
的相同方法,这是一个激励因素。为了真正代表一个过滤后的列表,几乎每个可能应该更改的操作,所以这并不像你为自己添加一堆工作。
Collection
的方法不是虚拟的,它们是密封的,这意味着将类类型作为基类将导致使用基类方法。这意味着当您认为已启用时,呼叫者可以绕过您添加的过滤器。这听起来真是个坏主意。
我的理论是,我正在覆盖和过滤基础Collection对象中的Items属性。我假设所有其他方法都会从这个集合中读取。
但是你不覆盖Items
,你隐藏它。您甚至需要使用new
关键字而不是override
关键字来表明您没有覆盖它。即使您愿意,也无法使用override
关键字,因为Collection
中的所有方法/属性都是sealed
,而不是virtual
。它专门设计为,以防止您做您正在尝试做的事情。 (这就是为什么我通常认为它作为一般课程很少使用。)
我知道我可以覆盖各种各样的Collection方法,但我真的不想全部覆盖它们。
从技术上讲,您必须覆盖的唯一虚拟方法是ClearItems
,InsertItem
,RemoveItem
和SetItem
(以及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
}