I'm trying to create a collection that acts just like all other collections, except when you read out of it, it can be filtered by setting a flag.
Consider this (admittedly contrived) example:
var list = new FilteredStringList() {
"Annie",
"Amy",
"Angela",
"Mary"
};
list.Filter = true;
I added four items, but then I set the "Filter" flag, so that whenever I read anything out of it (or do anything that involves reading out of it), I want to filter the list to items that begin with "A".
Here's my class:
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;
}
}
}
My theory is that I'm overriding and filtering the Items property from the base Collection object. I assumed that all other methods would read from this collection.
If I iterate via ForEach, I get:
Annie
Amy
Angela
Yay! But, if I do this:
list.Count()
I get "4". So, Count() is not respecting my filter.
And if I do this:
list.Where(i => i[1] == 'a')
I still get "Mary" back, even though she shouldn't be there.
I know I can override all sorts of the Collection methods, but I don't really want to override them all. Additionally, I want all the LINQ methods to respect my filter.
It sounds like your concept is to have one list that can be represented in two different ways. That's really two lists, and I would suggest running it as two lists. Furthering your contrived example:
// the base list containing all items
var list = new List<string>
{
"Annie", "Amy", "Angela", "Mary"
};
Then, create an expression of that list that only is your filtered items:
// do NOT .ToList() this list:
var filteredList = list.Where (x => x.StartsWith("A"));
You can continue to add/remove items from list
and filteredList
will always contain the updated values when you access it.
And if you really must access only a single field/property then you could put list
and filteredList
into a class that also includes an IsFiltered
property that is used to determine which of those two lists to return.
What you would be doing is creating a class that returns to you the proper list. Something like:
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;
}
}
Instead of binding to list
like your original example, you're binding to the above class's MyCollection
property ( filtered.MyCollection
);
Don't inherit from Collection
. Instead just inherit directly from Object
, have the method implement whatever interfaces are needed ( IList
, probably), create an internal List
instance to be the backing collection, and then expose each operation as you would like it to be seen.
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
}
There are several advantages here.
It's clear to you what each method is doing. You don't need to worry about having methods left with defaults that aren't sensible. The fact that only a handful of operations are likely to literally just calling the same method of the List
is a motivating factor here. To truly represent a filtered list just about every single operation likely should change, so it's not like you're adding a bunch of work for yourself.
The methods of Collection
aren't virtual, they're sealed, this means that having the class typed as the base class will result in the base class methods being used. This means a caller can bypass the filters you're adding when you think that they're enabled. That sounds like a really bad idea.
My theory is that I'm overriding and filtering the Items property from the base Collection object. I assumed that all other methods would read from this collection.
But you're not overriding Items
, you're hiding it. You even needed to call out the fact that you're not overriding it by using the new
keyword instead of the override
keyword. You couldn't use the override
keyword even if you wanted to because all of the methods/properties in Collection
are sealed
, not virtual
. It was designed specifically to prevent you from doing what you're trying to do . (Which is why I generally see little use for it as a class in general.)
I know I can override all sorts of the Collection methods, but I don't really want to override them all.
Well, technically, the only virtual methods that you have to override are ClearItems
, InsertItem
, RemoveItem
, and SetItem
(along with Equals
, GetHashCode
, and ToString
from Object
). The rest of the methods/properties aren't virtual, and so cannot be overridden. The only real effective solution is to write them all out.
Why not just use the plain old List
and apply Where
clause when needed?
var list = new List<string>() {
"Annie",
"Amy",
"Angela",
"Mary"
};
list.Where(o=>o.StartsWith("A")).Count();
Maybe override the Add
method instead? That way you'll cover all bases.
public class FilteredStringList : Collection<string>
{
public new void Add(string item)
{
if (item.StartsWith("A", StringComparison.InvariantCultureIgnoreCase))
base.Add(item);
}
}
My theory is that I'm overriding and filtering the Items property from the base Collection object. I assumed that all other methods would read from this collection.
Note that this is a misconception of how hiding with the new
modifier works. When you hide a property using protected new IList<string> Items
, it doesn't mean the base Items
property is now virtual. References to FilteredStringList
will use the new method, but references to Collection<string>
(including all methods in the base class) will continue to use the "hidden" base method.
Edit per comment
To have a collection that can switch the filter on/off, consider implementing one of the interfaces like ICollection<T>
. That way you can apply whatever filtering logic you like (ie, filter on add, filter on retrieve, etc). For example, if the Filter should apply on write, then you'd implement a custom Add
, while the GetEnumerator
methods would just be a pass-through. If the Filter should apply on read, then vice versa: Add
would be a pass-through, and the enumerator methods would implement the filter:
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
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.