I have something link this:
public abstract class Wrapper<T, TWrapped>: where TWrapped : Wrapper<T, TWrapped>
{
protected T baseObject;
protected ICollection<T> baseList;
protected ICollection<TWrapped> wrappedList;
public Wrapper (T base, ICollection<T> baseList, ICollection<TWrapped> wrappedList) { }
}
Then when I derive from it I need to to something like:
public class Base { }
public class Sample: Wrapper<Base, Sample> { }
Is there a way to remove the TWrapped and create a reference to the derived type? I tried using ICollection<Wrapped<T>> but then I remember that there is no covariance in ICollection.
EDIT: Clarifications, what I want with this wrapper is provide removal funcionality (and some other things) within the object (I can't change the base object so I need a wrapper to give this funcionality and manipulate it). This abstract class will have methods like this:
void Remove()
{
while(this.baseList.Remove(baseObject));
this.baseList = null;
while(this.wrappedList.Remove((TWrapped)this));
this.wrappedList = null;
}
I end up changing the logic of how I'm going to make the lists sync and allow Items to remove themselves. I created a new class to hold a collection of the wrapped items:
public interface IWrapper<TModel>
{
TModel Model { get; }
}
public class WrapperCollection<TWrapper, TModel> : ObservableCollection<TWrapper> where TWrapper : IWrapper<TModel>
{
protected IList<TModel> modelList;
public ReadOnlyObservableCollection<TWrapper> AsReadOnly { get; private set; }
protected WrapperCollection(IList<TModel> modelList)
{
this.modelList = modelList;
AsReadOnly = new ReadOnlyObservableCollection<TWrapper>(this);
}
public WrapperCollection(IList<TModel> modelList, Func<TModel, TWrapper> newWrapper)
:this(modelList)
{
foreach (TModel model in modelList)
this.Items.Add(newWrapper(model));
}
public WrapperCollection(IList<TModel> modelList, Func<TModel, WrapperCollection<TWrapper, TModel>, TWrapper> newWrapper)
: this(modelList)
{
foreach (TModel model in modelList)
this.Items.Add(newWrapper(model, this));
}
protected override void ClearItems()
{
modelList.Clear();
base.ClearItems();
}
protected override void InsertItem(int index, TWrapper item)
{
modelList.Insert(index, item.Model);
base.InsertItem(index, item);
}
protected override void RemoveItem(int index)
{
modelList.RemoveAt(index);
base.RemoveItem(index);
}
protected override void SetItem(int index, TWrapper item)
{
modelList[index] = item.Model;
base.SetItem(index, item);
}
}
Using the sample class:
public class wrappedInt: IWrapper<int>
{
private WrapperCollection<wrappedInt, int> list;
public Model { get; private set; }
public wrappedInt(int source, WrapperCollection<wrappedInt, int> list)
{
this.Model = source;
this.list = list;
}
public void RemoveMe()
{
if (list != null)
{
list.Remove(this);
list = null;
}
}
}
Then I can instantiate a collection with new WrapperCollection<wrappedInt, int>(listOfInts, (model, parent) => new wrappedInt(model, parent));.
Related
I try to give my generic interface a base.
Declaration:
public interface IMyInterface
{
ObservableCollection<object> Items {get;}
}
public interface IMyInterface<TValue>
{
new ObservableCollection<TValue> Items {get;} //Try to override base Items
}
Implementation
public abstract class MyBase<T> : IMyInterface<T>
{
private ObservableCollection<T> = _Items;
public ObservableCollection<T> Items
{
get
{
return _Items;
}
}
public ObservableCollection<T> IMyInterface.Items
{
get
{
return _Items as ObservableCollection<T>; //Reason?
}
}
Usage:
void foo(object sender, NotifyCollectionChangedEventArgs e)
{
foreach(IMyInterface obj in e.NewItems) // e.NewItems are derived from MyBase
{
var item = obj.Items; // Problem: item = null
//Do something
}
}
But if i try to use it in that way, i got null.
What is my mistake? Exists any other, better ways?
Your implementation is the problem here. The cast will result in null. You can only use covariant interfaces, so the first Items should be an IEnumerable<object>:
public interface IMyInterface
{
IEnumerable<object> Items { get; }
}
public interface IMyInterface<TValue> : IMyInterface
{
new ObservableCollection<TValue> Items { get; } //Try to override base Items
}
public abstract class MyBase<T> : IMyInterface<T> where T : class
{
private ObservableCollection<T> _Items;
public ObservableCollection<T> Items
{
get
{
return _Items;
}
}
IEnumerable<object> IMyInterface.Items
{
get
{
return _Items;
}
}
}
I am passing information between a SQL database and a PLC using 3rd party OPC libraries.
There are essentially two transactions.
Information passed from the PLC to the SQL server is statically typed. Very specific data is captured by the PLC and passed to the SQL database.
Information passed from the SQL server to the PLC is dynamically typed and may be limited to a single property or hundreds.
ITransaction.cs
public interface ITransaction : INotifyPropertyChanged
{
short Response { get; set; }
bool Request { get; set; }
void Reset();
}
BaseTransaction.cs
internal abstract class BaseTransaction<T> : IDisposable
where T : class, INotifyPropertyChanged
{
private T _opcClient;
protected T OpcClient
{
get { return _opcClient; }
set
{
if (_opcClient != value)
{
OnOpcClientChanging();
_opcClient = value;
OnOpcClientChanged();
}
}
}
protected abstract void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e);
private void OnOpcClientChanged()
{
if (_opcClient != null)
{
_opcClient.PropertyChanged += OnOpcClientPropertyChanged;
OpcManager = new OpcManager(_opcClient);
}
}
private void OnOpcClientChanging()
{
if (_opcClient != null)
_opcClient.PropertyChanged -= OnOpcClientPropertyChanged;
}
}
StaticTransaction.cs
internal abstract class StaticTransaction<T> : BaseTransaction<T>
where T : class, ITransaction, new()
{
public StaticTransaction()
{
OpcClient = new T();
}
protected override void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Response":
ProcessResponse(OpcClient.Response);
break;
case "Request":
ProcessRequest(OpcClient.Request);
break;
}
}
}
DynamicTransaction.cs
internal abstract class DynamicTransaction : BaseTransaction<ExpandoObject>
{
protected new dynamic OpcClient
{
get { return base.OpcClient as dynamic; }
}
public DynamicTransaction()
{
dynamic opcClient = new ExpandoObject();
opcClient.Request = false;
opcClient.Response = 0;
// Access database, use IDictionary interface to add properties to ExpandoObject.
opcClient.Reset = new Action(Reset);
base.OpcClient = opcClient;
}
protected override void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Response":
ProcessResponse(OpcClient.Response);
break;
case "Request":
ProcessRequest(OpcClient.Request);
break;
}
}
private void Reset()
{
// Use IDictionary interface to reset dynamic properties to defaults.
OpcClient.Request = false;
OpcClient.Response = 0;
}
}
As shown both StaticTransaction and DynamicTransaction have identical implementations of OnOpcClientPropertyChanged among other methods not shown. I would like to bring OnOpcClientPropertyChanged and the other methods into the base class but am prevented from doing so because the base class is unaware of the Response and Request properties found in the OpcClient. Can I bring the interface ITransaction into the base class somehow and still accommodate the dynamic implementation?
You can subclass DynamicObject (which acts just like ExpandoObject) and make your own version that implements ITransaction. This lets you move the ITransaction constraint up to the base class.
BaseTransaction.cs
internal abstract class BaseTransaction<T> : IDisposable where T : class, ITransaction
{
private T _opcClient;
protected T OpcClient
{
get { return _opcClient; }
set
{
if (_opcClient != value)
{
OnOpcClientChanging();
_opcClient = value;
OnOpcClientChanged();
}
}
}
private void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Response":
ProcessResponse(OpcClient.Response);
break;
case "Request":
ProcessRequest(OpcClient.Request);
break;
}
}
protected abstract void ProcessResponse(short opcClientResponse);
protected abstract void ProcessRequest(bool opcClientRequest);
private void OnOpcClientChanged()
{
if (_opcClient != null)
{
_opcClient.PropertyChanged += OnOpcClientPropertyChanged;
OpcManager = new OpcManager(_opcClient);
}
}
private void OnOpcClientChanging()
{
if (_opcClient != null)
_opcClient.PropertyChanged -= OnOpcClientPropertyChanged;
}
}
StaticTransaction.cs
internal abstract class StaticTransaction<T> : BaseTransaction<T>
where T : class, ITransaction, new()
{
public StaticTransaction()
{
OpcClient = new T();
}
}
DynamicTransactionObject.cs
internal class DynamicTransactionObject : DynamicObject, ITransaction, IDictionary<string, object>
{
private readonly Dictionary<string, object> _data = new Dictionary<string, object>();
public DynamicTransactionObject()
{
//Set initial default values for the two properties to populate the entries in the dictionary.
_data[nameof(Response)] = default(short);
_data[nameof(Request)] = default(bool);
}
public short Response
{
get
{
return (short)_data[nameof(Response)];
}
set
{
if (Response.Equals(value))
return;
_data[nameof(Response)] = value;
OnPropertyChanged();
}
}
public bool Request
{
get
{
return (bool)_data[nameof(Request)];
}
set
{
if (Request.Equals(value))
return;
_data[nameof(Request)] = value;
OnPropertyChanged();
}
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return _data.Keys;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return _data.TryGetValue(binder.Name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
object oldValue;
_data.TryGetValue(binder.Name, out oldValue)
_data[binder.Name] = value;
if(!Object.Equals(oldValue, value)
OnPropertyChanged(binder.Name);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#region IDictionary<string,object> members
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_data).GetEnumerator();
}
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
((ICollection<KeyValuePair<string, object>>)_data).Add(item);
}
void ICollection<KeyValuePair<string, object>>.Clear()
{
_data.Clear();
}
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
return ((ICollection<KeyValuePair<string, object>>)_data).Contains(item);
}
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
((ICollection<KeyValuePair<string, object>>)_data).CopyTo(array, arrayIndex);
}
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
return ((ICollection<KeyValuePair<string, object>>)_data).Remove(item);
}
int ICollection<KeyValuePair<string, object>>.Count
{
get { return _data.Count; }
}
bool ICollection<KeyValuePair<string, object>>.IsReadOnly
{
get { return ((ICollection<KeyValuePair<string, object>>)_data).IsReadOnly; }
}
bool IDictionary<string, object>.ContainsKey(string key)
{
return _data.ContainsKey(key);
}
void IDictionary<string, object>.Add(string key, object value)
{
_data.Add(key, value);
}
bool IDictionary<string, object>.Remove(string key)
{
return _data.Remove(key);
}
bool IDictionary<string, object>.TryGetValue(string key, out object value)
{
return _data.TryGetValue(key, out value);
}
object IDictionary<string, object>.this[string key]
{
get { return _data[key]; }
set { _data[key] = value; }
}
ICollection<string> IDictionary<string, object>.Keys
{
get { return _data.Keys; }
}
ICollection<object> IDictionary<string, object>.Values
{
get { return _data.Values; }
}
#endregion
}
DynamicTransaction.cs
internal abstract class DynamicTransaction : BaseTransaction<DynamicTransactionObject>
{
protected new dynamic OpcClient
{
get { return base.OpcClient as dynamic; }
}
public DynamicTransaction()
{
var opcClient = new DynamicTransactionObject();
// Access database, use IDictionary<string,object> interface to add properties to DynamicObject.
base.OpcClient = opcClient;
}
}
I want to use Generic for reusability.
Code to be applied are listed below.
pubic Class Test < T >
{
T item;
...
public void set(T item)
{
this.item = item;
// if (T type == int) {...}
// if (T type == string) {...}
abc();
}
private void abc()
{
...
}
}
Question1. I heard that using attribute is best solution in this situation.
How do I implement this? Please, Tell me if you have any example.
(Type will be added continually)
Question2. Is using Generic best solution about above example??
Thanks.
You should avoid checking for particular types inside generic methods and classes. You could make set a template method and then override the type-specific behaviour inside subclasses which specify the type T e.g.
public class Test<T> {
public void Set(T item) {
this.item = item;
this.OnSet(item);
abc();
}
protected virtual void OnSet(T item) { }
}
public class IntTest : Test<int> {
protected override void OnSet(int item) { ... }
}
public class StringTest : Test<string> {
protected override void OnSet(string item) { ... }
}
I think you're looking for:
if (item is int) else if(item is string)...
Whether it's the best approach or not, I leave up to others.
I have class with multiple properties. Sometimes a property (A) may be edited in a propertygrid. But sometimes property A may not be edited. This depends on a value of another property.
How can I do this?
EDIT:
I am sorry, I forget to mention that I want this in design-time.
Runtime property models are an advanced topic. For PropertyGrid the easiest route would be to write a TypeConverter, inheriting from ExpandableObjectConverter. Override GetProperties, and swap the property in question for a custom one.
Writing a PropertyDescriptor from scratch is a chore; but in this case you mainly just need to chain ("decorator") all the methods to the original (reflective) descriptor. And just override IsReadOnly to return the bool you want.
By no means trivial, but achievable.
using System;
using System.ComponentModel;
using System.Windows.Forms;
static class Program
{
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.Run(new Form { Text = "read only",
Controls = {
new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = new Foo { IsBarEditable = false }}
}
});
Application.Run(new Form { Text = "read write",
Controls = {
new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = new Foo { IsBarEditable = true }}
}
});
}
}
[TypeConverter(typeof(Foo.FooConverter))]
class Foo
{
[Browsable(false)]
public bool IsBarEditable { get; set; }
public string Bar { get; set; }
private class FooConverter : ExpandableObjectConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
var props = base.GetProperties(context, value, attributes);
if (!((Foo)value).IsBarEditable)
{ // swap it
PropertyDescriptor[] arr = new PropertyDescriptor[props.Count];
props.CopyTo(arr, 0);
for (int i = 0; i < arr.Length; i++)
{
if (arr[i].Name == "Bar") arr[i] = new ReadOnlyPropertyDescriptor(arr[i]);
}
props = new PropertyDescriptorCollection(arr);
}
return props;
}
}
}
class ReadOnlyPropertyDescriptor : ChainedPropertyDescriptor
{
public ReadOnlyPropertyDescriptor(PropertyDescriptor tail) : base(tail) { }
public override bool IsReadOnly
{
get
{
return true;
}
}
public override void SetValue(object component, object value)
{
throw new InvalidOperationException();
}
}
abstract class ChainedPropertyDescriptor : PropertyDescriptor
{
private readonly PropertyDescriptor tail;
protected PropertyDescriptor Tail { get {return tail; } }
public ChainedPropertyDescriptor(PropertyDescriptor tail) : base(tail)
{
if (tail == null) throw new ArgumentNullException("tail");
this.tail = tail;
}
public override void AddValueChanged(object component, System.EventHandler handler)
{
tail.AddValueChanged(component, handler);
}
public override AttributeCollection Attributes
{
get
{
return tail.Attributes;
}
}
public override bool CanResetValue(object component)
{
return tail.CanResetValue(component);
}
public override string Category
{
get
{
return tail.Category;
}
}
public override Type ComponentType
{
get { return tail.ComponentType; }
}
public override TypeConverter Converter
{
get
{
return tail.Converter;
}
}
public override string Description
{
get
{
return tail.Description;
}
}
public override bool DesignTimeOnly
{
get
{
return tail.DesignTimeOnly;
}
}
public override string DisplayName
{
get
{
return tail.DisplayName;
}
}
public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter)
{
return tail.GetChildProperties(instance, filter);
}
public override object GetEditor(Type editorBaseType)
{
return tail.GetEditor(editorBaseType);
}
public override object GetValue(object component)
{
return tail.GetValue(component);
}
public override bool IsBrowsable
{
get
{
return tail.IsBrowsable;
}
}
public override bool IsLocalizable
{
get
{
return tail.IsLocalizable;
}
}
public override bool IsReadOnly
{
get { return tail.IsReadOnly; }
}
public override string Name
{
get
{
return tail.Name;
}
}
public override Type PropertyType
{
get { return tail.PropertyType; }
}
public override void RemoveValueChanged(object component, EventHandler handler)
{
tail.RemoveValueChanged(component, handler);
}
public override void ResetValue(object component)
{
tail.ResetValue(component);
}
public override void SetValue(object component, object value)
{
tail.SetValue(component, value);
}
public override bool ShouldSerializeValue(object component)
{
return tail.ShouldSerializeValue(component);
}
public override bool SupportsChangeEvents
{
get
{
return tail.SupportsChangeEvents;
}
}
}
This answer assumes you are talking about WinForms. If you would like to change one property's readonly state based on another, you will need to have your object implement ICustomTypeDescriptor. This isn't a simple thing to do, but it will give you lots of flexibility about how your class is displayed in the propertygrid.
I've offered a similar solution in the past via this stack solution. It makes use of a custom property, and conditionally ignores an attempt to change at design-time vs run-time, but I'm sure could be altered in the SETter by applying your own "criteria" to allow it being changed...
I am serializing Lists of classes which are my data entities. I have a DataProvider that contains a List.
I always modify items directly within the collection.
What is the best way of determining if any items in the List have changed? I am using the Compact Framework.
My only current idea is to create a hash of the List (if that's possible) when I load the list. Then when I do a save I re-get the hash of the list and see if they're different values. If they're different I save and then update the stored Hash for comparison later, if they're the same then I don't save.
Any ideas?
If the items you add to the list implement the INotifyPropertyChanged interface, you could build your own generic list that hooks the event in that interface for all objects you add to the list, and unhooks the event when the items are removed from the list.
There's a BindingList<T> class in the framework you can use, or you can write your own.
Here's a sample add method, assuming the type has been declared with where T: INotifyPropertyChanged:
public void Add(T item)
{
// null-check omitted for simplicity
item.PropertyChanged += ItemPropertyChanged;
_List.Add(item);
}
and the this[index] indexer property:
public T this[Int32 index]
{
get { return _List[index]; }
set {
T oldItem = _List[index];
_List[index] = value;
if (oldItem != value)
{
if (oldItem != null)
oldItem.PropertyChanged -= ItemPropertyChanged;
if (value != null)
value.PropertyChanged += ItemPropertyChanged;
}
}
}
If your items doesn't support INotifyPropertyChanged, but they're your classes, I would consider adding that support.
You could create your own IList<T> class, say DirtyList<T> that can record when the list has changed.
If you're willing to use reflection, the List<T> class has a private field called _version that is incremented every time the list changes. It won't tell you which items have changed, but you can compare it with the original value of _version to detect an unmodified list.
For reference, this field is used to ensure that enumerators become invalid when the list is modified. So you should be able to use it for your purposes fairly reliably, unless the actual managed code for List<T> changes.
To get the value of _version you can use something like this:
List<T> myList;
var field = myList.GetType().GetField("_version", BindingFlags.Instance | BindingFlags.NonPublic);
int version = field.GetValue(myList);
Generally speaking, though, this isn't the best approach. If you're stuck using a List<T> that someone else created, however, it's probably the best option you have. Please be aware that changes to the .NET framework could change the name of the field (or remove it entirely), and it's not guaranteed to exist in third-party CLR implementations like Mono.
How about something like this?
public class ItemChangedArgs<T> : EventArgs
{
public int Index { get; set; }
public T Item { get; set; }
}
public class EventList<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable
{
private List<T> m_list;
public event EventHandler<ItemChangedArgs<T>> ItemAdded;
public event EventHandler<ItemChangedArgs<T>> ItemRemoved;
public event EventHandler<ItemChangedArgs<T>> ItemChanged;
public event EventHandler ListCleared;
public EventList(IEnumerable<T> collection)
{
m_list = new List<T>(collection);
}
public EventList(int capacity)
{
m_list = new List<T>(capacity);
}
public EventList()
{
m_list = new List<T>();
}
public void Add(T item)
{
Add(item, true);
}
public void Add(T item, Boolean raiseEvent)
{
m_list.Add(item);
if (raiseEvent) RaiseItemAdded(this.Count - 1, item);
}
public void AddRange(IEnumerable<T> collection)
{
foreach (T t in collection)
{
m_list.Add(t);
}
}
private void RaiseItemAdded(int index, T item)
{
if (ItemAdded == null) return;
ItemAdded(this, new ItemChangedArgs<T> { Index = index, Item = item });
}
public int IndexOf(T item)
{
return m_list.IndexOf(item);
}
public void Insert(int index, T item)
{
m_list.Insert(index, item);
RaiseItemAdded(index, item);
}
public void RemoveAt(int index)
{
T item = m_list[index];
m_list.RemoveAt(index);
RaiseItemRemoved(index, item);
}
private void RaiseItemRemoved(int index, T item)
{
if(ItemRemoved == null) return;
ItemRemoved(this, new ItemChangedArgs<T> { Index = index, Item = item });
}
public T this[int index]
{
get { return m_list[index]; }
set
{
m_list[index] = value;
RaiseItemChanged(index, m_list[index]);
}
}
private void RaiseItemChanged(int index, T item)
{
if(ItemChanged == null) return;
ItemChanged(this, new ItemChangedArgs<T> { Index = index, Item = item });
}
public void Clear()
{
m_list.Clear();
RaiseListCleared();
}
private void RaiseListCleared()
{
if(ListCleared == null) return;
ListCleared(this, null);
}
public bool Contains(T item)
{
return m_list.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
m_list.CopyTo(array, arrayIndex);
}
public int Count
{
get { return m_list.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
for (int i = 0; i < m_list.Count; i++)
{
if(item.Equals(m_list[i]))
{
T value = m_list[i];
m_list.RemoveAt(i);
RaiseItemRemoved(i, value);
return true;
}
}
return false;
}
public IEnumerator<T> GetEnumerator()
{
return m_list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return m_list.GetEnumerator();
}
}
Assuming that GetHashCode() for every member contained in the list is implemented properly (and thus changes when an element changes) I'd imagine something along the lines of:
public class DirtyList<T> : List<T> {
private IList<int> hashCodes = new List<int> hashCodes();
public DirtyList() : base() { }
public DirtyList(IEnumerable<T> items) : base() {
foreach(T item in items){
this.Add(item); //Add it to the collection
hashCodes.Add(item.GetHashCode());
}
}
public override void Add(T item){
base.Add(item);
hashCodes.Add(item);
}
//Add more logic for the setter and also handle the case where items are removed and indexes change and etc, also what happens in case of null values?
public bool IsDirty {
get {
for(int i = 0; i < Count: i++){
if(hashCodes[i] != this[i].GetHashCode()){ return true; }
}
return false;
}
}
}
*Please be aware i typed this up on SO and do not have a compiler, so above stated code is in no way guarenteed to work, but hopefully it'll show the idea.
You could implement you're own list that maintains 2 internal lists... and instantiated version and tracking version... e.g.
//Rough Psuedo Code
public class TrackedList<T> : List<T>
{
public bool StartTracking {get; set; }
private List<T> InitialList { get; set; }
CTOR
{
//Instantiate Both Lists...
}
ADD(item)
{
if(!StartTracking)
{
Base.Add(item);
InitialList.Add(item);
}
else
{
Base.Add(item);
}
}
public bool IsDirty
{
get
{
Check if theres any differences between initial list and self.
}
}
}
Make sure that T is a descendant of an object that has a dirty flag and have the IList implementation have a check for that which walks the list's dirty flags.