I have a composite property called Items of type List to an order class. On the GUI the user fills out some fields like Name, Description, Price, Quantity, etc... and then clicks the Add Item button which of course adds the item to the order's list of items. What I'd like to do is create a method that checks the item's IsComplete property which does a check to ensure the required properties are set so that way someone can't just call order.Items.Add(item) if it isn't complete. If it's not I'd like an exception to be thrown if the item's IsComplete property returns false... What would be an easy way to go about this?
This can be achieved by sub-classing List<T> into a derived class, and then overriding the Add method, like so.
public class MyItemCollection : List<MyItem>
{
public override void Add(MyItem item)
{
if (item.IsComplete)
{
base.Add(item);
}
else
{
throw new InvalidOperationException("Unable to add an incomplete item");
}
}
}
Your order class would then have the property MyItemCollection rather than List<T>, like so:
public class Order
{
public MyItemCollection Items { get; set; }
}
You can also use ObservableCollection<T>: http://msdn.microsoft.com/en-us/library/ms668604.aspx
It implements INotifyCollectionChanged: http://msdn.microsoft.com/en-us/library/System.Collections.Specialized.INotifyCollectionChanged.aspx
Since the method Add(T) is not virtual you can't override, it.
ObservableCollection allow to throw an event when an element was added but not to undo this add.
You can implement the interface IList<T> with a List<T> storred internaly and add the desired verification in the method Add(T item) before calling the _list.Add(item) like in the exemple below :
public class MyItemCollection : IList<MyItem>
{
private List<MyItem> _list;
public MyItemCollection()
{
_list = new List<MyItem>();
}
public void Add(MyItem item)
{
if (item.IsComplete)
{
_list.Add(item);
}
else
{
throw new InvalidOperationException("Unable to add an incomplete item");
}
}
//Then you have to implement all the IList interface members...
}
The only problem with this solution is that it require to write a lot of boilerplate code.
If only one class is responsible of the manipulation of your List, you can also decide to implement a method AddToMyItemCollection(MyItem item) in the responsible class. It is even a good practive as it's respect the GRASP pattern protected variation (Instance.getC() is preferable to Instance.getA().getB().getC())
Related
I have a data structure organised as such:
A List<Graphic> containing a List<Symbol> which contains a List<Alias> amongst other things.
I want to be able to run a function within the Graphic class whenever anything changes within an alias/symbol/graphic. The best way that I can see to do this would be to implement IPropertyChanged on each of the three classes. However, is it possible to cascade these whilst getting a reference to the Graphic as to what exactly changed?
Note: The changes will generally be to the properties within an Alias but it is just as plausible that a Symbol could be removed/added or renamed.
You can leverage class ObservableCollection<T> that implements INotifyCollectionChanged and INotifyPropertyChanged
Basically, you need to create a derived class and override some methods
public class Data
{
public ObservableCollection<String> InnerCollection { get; set; }
}
public class collection : ObservableCollection<Data>
{
protected override void InsertItem(int index, Data item)
{
item.InnerCollection.CollectionChanged += InnerCollection_CollectionChanged;
base.InsertItem(index, item);
}
private void InnerCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//Actually it does not make any sense. You may need to construct something special. But firing an event it would be enough
OnCollectionChanged(e);
}
protected override void RemoveItem(int index)
{
var date = base.Items[index];
date.InnerCollection.CollectionChanged -= InnerCollection_CollectionChanged;
base.RemoveItem(index);
}
}
Using something like this, you can nest your events as deep as you want.
What is the best practice for having a List<T> in the business layer that needs to be displayed on a UI? I currently use databinding with an ObservableCollection<T> in the viewmodel that duplicates the data of the List<T>. The obvious problem of this is when the List<T> is modified in the business layer the ObservableCollection<T> needs to be recreated so the changes of the List<T> are reflected in the UI. This can't be the best way.
I also will not accept using an ObservableCollection<T> in the business layer as an answer.
Thanks!
If you insist on having List<T> and separate events notifying about list modification, then duplication is the only sensible way.
If you have ListChanged event with no details on what was actually changed, you can avoid duplication and just wrap the list in a proxy collection implementing INotifyCollectionChanged interface which will fire appropriate CollectionChanged events in NotifyCollectionChangedAction.Reset mode.
If you have granular ItemChanged, ItemAdded etc. events, then you're effectively duplicating ObservableCollection<T> functionality. In this case, you can wrap your application in a proxy collection implementing INotifyCollectionChanged interface, but which understands your architecture and translates events into appropriate NotifyCollectionChangedAction.
Having ObservableCollection<T> in business layer isn't a bad idea at all. It is a specialized collection which provides common interface to notify about item changes, not some class designed specifically for WinForms or WPF or whatever.
You can implement the INotifyCollectionChanged interface, but if you want to use it in a way that you can hold on your implementation the collection in case implementing a class of your own also holding an implementation of IEnumerable will do a lot of the work for you a for instance is what follows, this is the base class i use for holding all the collections that will be updated, on this implementation there is also an ordering consideration in the variable _ordering:
public abstract class BaseINotifyCollectionChanged<T, K> : INotifyCollectionChanged, IEnumerable<T>
{
Func<T, K> _ordering;
bool _ascending;
public BaseINotifyCollectionChanged()
{
}
public BaseINotifyCollectionChanged(Func<T, K> ordering, bool ascending = true)
{
_ordering = ordering;
_ascending = ascending;
OnCollectionChanged();
}
protected abstract IList<T> GetCollection();
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected void OnCollectionChanged()
{
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void RaiseCollectionChanged()
{
OnCollectionChanged();
}
public IEnumerator<T> GetEnumerator()
{
return _ordering == null ? GetCollection().GetEnumerator() : _ascending ? GetCollection().OrderBy<T, K>(_ordering).GetEnumerator() :
GetCollection().OrderByDescending<T, K>(_ordering).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _ordering == null ? GetCollection().GetEnumerator() : _ascending ? GetCollection().OrderBy<T, K>(_ordering).GetEnumerator() :
GetCollection().OrderByDescending<T, K>(_ordering).GetEnumerator();
}
}
}
When you have this implementation you can use as you wish and through out all the collections you need on your app, working on some dry for yourself here is one example of the use you can give to this abstract class:
public class Categories : BaseINotifyCollectionChanged<Category, string>
{
long _onCategoryRoot;
public void SetOnCategoryRoot(long categoryId)
{
_onCategoryRoot = categoryId;
RaiseCollectionChanged();
}
protected override IList<Category> GetCollection()
{
Category category = new Category();
return _onRoot ? category.GetRootCategories() : category.GetSubCategoriesOnRoot(_onCategoryRoot);
}
}
When you set a _onCategoryRoot in the class the collection you will be displaying will be updated via the RaiseCollectionChanged() method, so you need to add in your viewmodel a property with the class Categories and set the binding in the XAML.
i have a list property like that
protected IList<System.Windows.Media.Color> colors;
public IList<System.Windows.Media.Color> Colors
{
get { return colors; }
set { colors = value; }
}
and i have a function
protected void updateBuffers()
which needs to be called each time a property user change the property, for example
Colors.Add(...)
...
Colors.Clear(...)
is there an elegent simple way to do that?
You could create a new object that created additional properties, using the List as a base class:
public class CustomList<T> : List<T>
{
public new void Add(T item) {
base.Add(item);
this.UpdateBuffers();
}
}
The 'new' keyword is required to completely overwrite the existing implementation of Add, which isn't marked as virtual in the base class.
Thanks to Hans Passant and LarsTech for their feedback in the comments.
You cannot with a plain list.
You can however create your own class implementing IList<T> or inherit from Collection<T>.
Or you can use an ObservableCollection<T> or any built-in class implementing INotifyCollectionChanged
see MSDN for documentation.
Moreover, ObservableCollection will allow you to use your collection in bindings if you are using them for populating UI in WPF or Windows 8 applications.
Is there a way to watch an object graph for changes on any object, and do something based on that change?
Lets say I have the following:
public class Main:INotifyPropertyChanged
{
public ObservableCollection<Foo> FooItems { get; }
public ObservableCollection<Bar> BarItems { get; }
}
public class Foo:INotifyPropertyChanged
public class Bar:INotifyPropertyChanged
{
public ObservableCollection<Other> OtherItems { get; }
}
public class Other:INotifyPropertyChanged
What would be the best way to implement some sort of change notification system across all objects? For example an autosave, where any change would trigger the system to serialize the Main class.
Should I have glue code in the Main class watching the BarItems for changes, hooking up to their PropertyChanged? This seems a bit messy, and error prone to me. Is there a better way?
Rather than objects raising their own property changed events, perhaps they could raise a shared event instead. For example:
public class SharedChangeNotifier
{
public static event EventHandler<DataChangedEventArgs> SharedChangeEvent;
protected void RaiseChangeEvent()
{
if (SharedChangeNotifier.SharedChangeEvent != null)
{
SharedChangeNotifier.SharedChangeEvent(
this, new DataChangedEventArgs());
}
}
}
public class Foo : SharedChangeNotifier
{
public int MyProperty
{
get { ... }
set
{
...
RaiseChangeEvent();
}
}
}
You could then attach an event handler to the static SharedChangeNotifier's SharedChangeEvent to be notified whenever any object deriving from SharedChangeNotifier is changed, like this:
SharedChangeNotifier.SharedChangeEvent += (sender, args) => {
DoWhatever();
};
I just read an interesting blog post on that issue at http://www.lennybacon.com/ReBlinderFleckChangeTracking.aspx
The post is in German, but as it's mostly code, it should be OK.
Hope this helps!
The way I have done it in the past was to create a separate ChangeTracker class with a method to Register objects into it. Inside that method, use reflection to explore the registered object, and hook into events on each of its properties that implements INotifyPropertyChanged.
You can then add methods to the ChangeTracker to interrogate the state, e.g. IsDirty(), or even implement INotifyPropertyChanged on the ChangeTracker.
(Be sure to implement and use IDisposable on the ChangeTracker, and drop all the event handlers at that time).
You could have the same handler for all items that implement INotifyPropertyChanged events:
foreach (INotifyPropertyChanged obj in FooItems)
obj.PropertyChanged+= this.modified;
// likewise for bar items, and when items are added
private void modified(object sender, EventArgs args)
{
this.Save();
}
edit> To do the same when an item is added:
private void addToList<T>(ref List<T> l, T item) where T : INotifyPropertyChanged
{
item.PropertyChanged += this.modified;
l.Add(item);
}
call it using:
Foo item = new Foo();
List<Foo> fooItems = new List<Foo>();
addToList<Foo>(ref fooItems, item);
Is it alright in a hierarchy to use the new keyword at some point to override the return type in a method?
Can I use virtual new or new virtual so I can override the return type?
I need to consider also classes that inherit from that point on. Can they override this method where the base was created with new?
You can do this, but the real question is whether you should do it.
The problem is that you'll get very unexpected behavior, depending on how your class is used. If you're calling your class from an instance of the base class, the original, non-"new" method will get called, which will probably be unexpected.
In general, I'd avoid using the new keyword to override a base class method unless there is a very distinct reason to do so - if your method is going to return a new type, declare it as a new method with a different name or signature instead of hiding the base class method - it will make your hierarchy much more usable.
Hypothetically....
public class BaseCollection<T>
{
// void return - doesn't seem to care about notifying the
// client where the item was added; it has an IndexOf method
// the caller can use if wants that information
public virtual void Add(T item)
{
// adds the item somewhere, doesn't say where
}
public int IndexOf(T item)
{
// tells where the item is
}
}
public class List<T> : BaseCollection<T>
{
// here we have an Int32 return because our List is friendly
// and will tell the caller where the item was added
new public virtual int Add(T item) // <-- clearly not an override
{
base.Add(item);
return base.IndexOf(item);
}
}
Here I use the "new" modifier because a List<T> reference will hide the Add method from the BaseCollection<T>. By default, hiding members from a base generates a warning from the compiler (an error if you have compilation set up to fail on warnings). So I'm basically telling the compiler... "Yeah I know I hid the Add method with the void return, it's desired functionality - just go with it."