Tipicamente quando ho bisogno di implementare un progetto, uso i parametri del modello generico nella classe base, così come nelle classi di raccolta, per specificare il tipo di bambino e / o il tipo genitore. Se si utilizzano i parametri del tipo, il codice comune nella classe base può essere riutilizzato indipendentemente dal tipo di figlio o genitore.
Detto questo, di solito è vantaggioso avere una classe base non generica per entrambi i bambini, i genitori o entrambi, per semplificare la progettazione. Potrebbe essere ancora necessario eseguire il cast dinamico occasionalmente. Inoltre, probabilmente hai ancora bisogno di interfacce, se il tipo genitore può variare in molti modi.
È possibile farlo interamente senza classi base, usando le tecniche wrapper descritte in un'altra risposta, ma io preferisco la soluzione più semplice. Il codice seguente utilizza un'interfaccia per il tipo genitore, per consentire alla classe di base di variare.
public interface IParent<TChild>
where TChild : class
{
/// <summary>
/// Invoked after a change occurs in a <see cref="ChildCollection{TParent, TChild}"/>.
/// </summary>
/// <param name="item">The relavent <typeparamref name="TChild"/>.</param>
/// <param name="changeType">The type of change that occured.</param>
void OnCollectionChanged(TChild item, CollectionChangeType changeType);
}
public class Model
{
object _parent;
protected Model() { }
/// <summary>
/// Raised when this <see cref="Model"/> changes.
/// </summary>
public event EventHandler Changed;
internal object ObjectParent
{
get { return _parent; }
set { _parent = value; }
}
/// <summary>
/// Call this method when the <see cref="Model"/> has changed.
/// Do not call if you use <see cref="Model.SetField{T}"/>:
/// SetField calls OnChanged automatically.
/// </summary>
/// <remarks>
/// If the parent is a <see cref="Model"/>,
/// <see cref="OnChildChanged"/> is called on the parent.
/// </remarks>
protected virtual void OnChanged()
{
Model modelParent = _parent as Model;
if (modelParent != null)
modelParent.OnChildChanged(this);
if (Changed != null)
Changed(this, EventArgs.Empty);
}
/// <summary>
/// Called after a child <see cref="Model"/> has changed.
/// </summary>
/// <param name="item">The child object that changed.</param>
protected internal virtual void OnChildChanged(Model item)
{
OnChanged();
}
/// <summary>
/// Called after a collection of child <see cref="Model"/>s has changed.
/// </summary>
/// <param name="item">The relevant child object.</param>
/// <param name="changeType">The type of change that occured.</param>
protected internal virtual void OnCollectionChanged(Model item, CollectionChangeType changeType)
{
OnChanged();
}
/// <summary>
/// Set the value of a field, calling <see cref="OnChanged"/> if the value changes.
/// </summary>
/// <typeparam name="T">Type of the field set.</typeparam>
/// <param name="field">A reference to the field to be modified.</param>
/// <param name="value"></param>
/// <returns></returns>
protected bool SetField<T>(ref T field, T value)
where T : IEquatable<T>
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
OnChanged();
return true;
}
}
/// <summary>
/// Base class for a model (non-visual) object contained
/// in a <see cref="ChildCollection{TChild, TParent}"/>.
/// </summary>
/// <typeparam name="TParent">
/// Type of the object containing a <see cref="ChildCollection{TChild, TParent}"/>.
/// </typeparam>
public abstract class Child<TParent> : Model
where TParent : class
{
/// <summary>
/// The object owning this <see cref="Child{TParent}"/>.
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public TParent Parent
{
get { return (TParent)ObjectParent; }
}
/// <summary>Set the <see cref="Child{TParent}.Parent"/> property.</summary>
/// <param name="parent">The new <see cref="Child{TParent}.Parent"/> value.</param>
protected internal virtual void Attach(TParent parent)
{
Check.ArgumentNotNull(parent, "parent");
Check.Operation(ObjectParent == null, "Already added to another collection.");
ObjectParent = parent;
}
/// <summary>
/// Set the <see cref="Child{TParent}.Parent"/> property to <see langword="null"/>.
/// </summary>
protected internal virtual void Detach()
{
Check.Operation(ObjectParent != null);
ObjectParent = null;
}
}
/// <summary>
/// A collection of <see cref="Child{TParent}"/> objects.
/// </summary>
/// <typeparam name="TChild">The type of the child object.</typeparam>
/// <typeparam name="TParent">The type of the parent object containing the collection.</typeparam>
public abstract class ChildCollection<TChild, TParent> : ICollection<TChild>
where TChild : Child<TParent>
where TParent : class
{
readonly TParent _parent;
readonly List<TChild> _items;
/// <summary>
/// Initialize a new instance of the <see cref="ChildCollection{TChild, TParent}"/> class.
/// </summary>
/// <param name="parent">The parent of the new collection.</param>
protected ChildCollection(TParent parent)
{
Check.ArgumentNotNull(parent, "parent");
_parent = parent;
_items = new List<TChild>();
}
/// <summary>
/// The number of items in this <see cref="ChildCollection{TChild, TParent}"/>.
/// </summary>
public int Count { get { return _items.Count; } }
/// <summary>
/// Get the <typeparamref name="TChild"/> at the specified index.
/// </summary>
/// <param name="index">Index of a <typeparamref name="TChild"/>.</param>
/// <returns>The <typeparamref name="TChild"/> at <paramref name="index"/>.</returns>
public virtual TChild this[int index]
{
get { return _items[index]; }
}
/// <summary>
/// Is this <see cref="ChildCollection{TChild, TParent}"/> read-only?
/// Always returns false.
/// </summary>
public bool IsReadOnly { get { return false; } }
/// <summary>
/// Add the specified <typeparamref name="TChild"/> to this
/// <see cref="ChildCollection{TChild, TParent}"/>.
/// </summary>
/// <param name="item">The <typeparamref name="TChild"/> to be added.</param>
public void Add(TChild item)
{
Check.ArgumentNotNull(item, "item");
AddCore(item);
OnChanged(item, CollectionChangeType.Added);
}
/// <summary>
/// Add all <typeparamref name="TChild"/> objects in the specified collection
/// to this <see cref="ChildCollection{TChild, TParent}"/>.
/// </summary>
/// <param name="items">The <typeparamref name="TChild"/> objects to be added.</param>
public void AddRange(IEnumerable<TChild> items)
{
Check.ArgumentNotNull(items, "items");
foreach (TChild item in items)
AddCore(item);
OnChanged(null, CollectionChangeType.Added);
}
void AddCore(TChild item)
{
if (Contains(item))
Remove(item);
item.Attach(_parent);
_items.Add(item);
}
/// <summary>
/// Is the specified <typeparamref name="TChild"/> contained in this
/// <see cref="ChildCollection{TChild, TParent}"/>?
/// </summary>
/// <param name="item">The <typeparamref name="TChild"/> to be found.</param>
/// <returns>
/// <see langword="true"/> if <paramref name="item"/> is contained in this
/// <see cref="ChildCollection{TChild, TParent}"/>; otherwise, <see langword="false"/>.
/// </returns>
public bool Contains(TChild item)
{
return item.Parent == _parent;
}
/// <summary>
/// Returns the index of the specified <typeparamref name="TChild"/> in this
/// <see cref="ChildCollection{TChild, TParent}"/>.
/// </summary>
/// <param name="item">The <typeparamref name="TChild"/> whose index is returned.</param>
/// <returns>
/// The index of <paramref name="item"/>, if found; -1 if not found.
/// </returns>
public int IndexOf(TChild item)
{
return Contains(item) ? _items.IndexOf(item) : -1;
}
/// <summary>
/// Remove the specified <typeparamref name="TChild"/> from this
/// <see cref="ChildCollection{TChild, TParent}"/>.
/// </summary>
/// <param name="item">The <typeparamref name="TChild"/> to be removed.</param>
/// <returns>
/// <see langword="true"/> if <paramref name="item"/> was removed;
/// <see langword="false"/> if <paramref name="item"/> is not contained
/// in this <see cref="ChildCollection{TChild, TParent}"/>.
/// </returns>
public bool Remove(TChild item)
{
Check.ArgumentNotNull(item, "item");
int index = IndexOf(item);
if (index < 0)
return false;
RemoveAt(index);
return true;
}
/// <summary>
/// Remove the item at the specified index.
/// </summary>
/// <param name="index">The index of the item to be removed.</param>
public virtual void RemoveAt(int index)
{
TChild item = _items[index];
item.Detach();
_items.RemoveAt(index);
OnChanged(item, CollectionChangeType.Removed);
if (_items.Count == 0)
OnChanged(null, CollectionChangeType.AllRemoved);
}
/// <summary>
/// Insert a <typeparamref name="TChild"/>
/// after a different <typeparamref name="TChild"/> in this
/// <see cref="ChildCollection{TChild, TParent}"/>.
/// </summary>
/// <param name="afterItem">
/// A <typeparamref name="TChild"/> already in this <see cref="ChildCollection{TChild, TParent}"/>.
/// </param>
/// <param name="item">The <typeparamref name="TChild"/> to be inserted.</param>
public void InsertAfter(TChild afterItem, TChild item)
{
int index = 0;
if (afterItem == null)
index = 1;
else
index = _items.IndexOf(afterItem) + 1;
Insert(index, item);
}
/// <summary>
/// Insert the specified <typeparamref name="TChild"/> at the specified index.
/// </summary>
/// <param name="index">
/// Zero-based index at which <paramref name="item"/> is to be inserted.
/// </param>
/// <param name="item">The <typeparamref name="TChild"/> to be inserted.</param>
public void Insert(int index, TChild item)
{
Check.ArgumentNotNull(item, "item");
int previousIndex = _items.IndexOf(item);
if (previousIndex == index)
return;
if (previousIndex >= 0)
{
_items.RemoveAt(previousIndex);
if (index > previousIndex)
--index;
_items.Insert(index, item);
}
else
{
_items.Insert(index, item);
item.Attach(_parent);
}
OnChanged(item, CollectionChangeType.Inserted);
}
/// <summary>
/// Remove all <typeparamref name="TChild"/> objects from this
/// <see cref="ChildCollection{TChild, TParent}"/>.
/// </summary>
public void Clear()
{
if (Count == 0)
return;
foreach (TChild item in _items)
item.Detach();
_items.Clear();
OnChanged(null, CollectionChangeType.AllRemoved);
}
/// <summary>
/// Copy all object references from this
/// <see cref="ChildCollection{TChild, TParent}"/> to the specified array.
/// </summary>
/// <param name="array">Destination where object references are copied.</param>
/// <param name="arrayIndex">Index in <paramref name="array"/> where copying begins.</param>
public void CopyTo(TChild[] array, int arrayIndex)
{
_items.CopyTo(array, arrayIndex);
}
/// <summary>
/// Get an enumerator suitable for enumerating all <typeparamref name="TChild"/> objects
/// in this <see cref="ChildCollection{TChild, TParent}"/>.
/// </summary>
/// <returns>An enumerator.</returns>
public IEnumerator<TChild> GetEnumerator()
{
return _items.GetEnumerator();
}
void OnChanged(TChild item, CollectionChangeType changeType)
{
IParent<TChild> parentParent = _parent as IParent<TChild>;
if (parentParent != null)
parentParent.OnCollectionChanged(item, changeType);
Model modelParent = _parent as Model;
if (modelParent != null)
modelParent.OnCollectionChanged(item, changeType);
}
}