IEditableObject的一個通用實現
IeditableObject是一個通用介面,用於支援物件編輯。當我們在介面上選擇一個條目,然後對其進行編輯的時候,接下來會有兩種操作,一個是保持編輯結果,一個取消編輯。這就要求我們保留原始值,否則我們只能到資料庫裡面再次查詢。IeditableObject介面的三個方法定義為我們定義了這個行為規範:
public interface IEditableObject
{
void BeginEdit();
//取消編輯,當副本恢復到當前物件,並清除副本
void CancelEdit();
// 接受編輯結果,並清除副本
}
對於IeditableObject的實現,應該滿足一下要求:
-
具有NonEditableAttribute標記的屬性不參與編輯
-
如果某個屬性型別也實現了IeditableObject, 那麼將遞迴呼叫相應編輯方法。
-
對於集合物件,如果集合物件實現了IeditableObject,將會對集合的每個項呼叫相應編輯方法。
-
可以查詢物件是否改變,包括任何標量屬性的變化,關聯的IeditableObject型別的屬性的變化,集合屬性的變化。
下面是具體實現:
首先要定義NonEditableAttribute類:
[AttributeUsage(AttributeTargets.Property,Inherited = true, AllowMultiple = false)]
public sealed class NonEditableAttribute : Attribute {}
其次是一個輔助類,用於找到一個型別內的標量屬性,可編輯物件屬性和集合屬性,因為這三種屬性需要不同的處理方式:
internal class EditableProperty
{
public EditableProperty(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
Scalars = new List<PropertyInfo>();
Editables = new List<PropertyInfo>();
Collections = new List<PropertyInfo>();
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
//忽略定義了NonEditableAttribute的屬性。
if (property.IsDefined(typeof(NonEditableAttribute), false))
{
continue;
}
//不能讀的屬性不參與編輯
if (!property.CanRead)
{
continue;
}
Type propertyType = property.PropertyType;
if (propertyType.IsValueType || propertyType == typeof(string))
{
//標量屬性需要是值型別或者string型別,並且可寫。
if (property.CanWrite)
{
Scalars.Add(property);
}
}
//可編輯物件屬性是遞迴參與編輯流程的。
else if ((typeof(IEditableObject).IsAssignableFrom(propertyType)))
{
Editables.Add(property);
}
//集合屬性也是參與編輯流程的。
else if (typeof(IList).IsAssignableFrom(propertyType))
{
Collections.Add(property);
}
}
}
public List<PropertyInfo> Scalars { get; private set; }
public List<PropertyInfo> Editables { get; private set; }
public List<PropertyInfo> Collections { get; private set; }
}
下面是可編輯物件的實現:
[Serializable]
public abstract class EditableObject : NotifiableObject, IEditableObject
{
//快取可編輯屬性,不用每次重新獲取這些元資料
private static ConcurrentDictionary<Type, EditableProperty> _cachedEditableProperties;
static EditableObject()
{
_cachedEditableProperties = new ConcurrentDictionary<Type, EditableProperty>();
}
//物件的副本
private object _stub;
private bool _isEditing;
//物件是不是處於編輯狀態。
[NonEditable]
public bool IsEditing
{
get { return _isEditing; }
protected set
{
if (_isEditing != value)
{
_isEditing = value;
base.OnPropertyChanged("IsEditing");
}
}
}
//獲取物件是不是改變了,比如說,呼叫了BeginEdit但是並沒有修改任何屬性,物件就沒有改變,
//此時不需要儲存,檢查修改的時候內部做了物件相互引用造成的無窮遞迴情況。所以即使物件有相互應用
//也能正確檢測。
[NonEditable]
public bool IsChanged
{
get
{
return GetIsChanged(new HashSet<EditableObject>());
}
}
//開始編輯
public void BeginEdit()
{
//如果已經處於編輯狀態,那麼什麼也不做。
if (IsEditing)
{
return;
}
IsEditing = true;
//建立物件副本。
if (this is ICloneable)
{
ICloneable cloneable = this as ICloneable;
_stub = cloneable.Clone();
}
else
{
_stub = MemberwiseClone();
}
var editableProp = GetEditableProperty();
//對於每個管理的IeditableObject,遞迴呼叫BeginEdit
foreach (var item in editableProp.Editables)
{
var editableObject = item.GetValue(this, null) as IEditableObject;
if (editableObject != null)
{
editableObject.BeginEdit();
}
}
//對於集合屬性中,如果任何項是IeditableObject,那麼遞迴呼叫BeginEdit。
foreach (PropertyInfo collProperty in editableProp.Collections)
{
IList coll = collProperty.GetValue(this, null) as IList;
if (coll != null)
{
foreach (IEditableObject editableObject in coll.OfType<IEditableObject>())
{
editableObject.BeginEdit();
}
}
}
}
//取消編輯
public void CancelEdit()
{
//如果沒有處於編輯狀態,就什麼也不做。
if (!IsEditing)
{
return;
}
IsEditing = false;
var editableProp = GetEditableProperty();
//還原標量屬性的值。
foreach (PropertyInfo scalarProperty in editableProp.Scalars)
{
scalarProperty.SetValue(this,scalarProperty.GetValue(_stub, null), null);
}
//對於IeditableObject屬性,遞迴呼叫CancelEdit
foreach (PropertyInfo editableProperty in editableProp.Editables)
{
IEditableObject editableObject = editableProperty.GetValue(this, null) as IEditableObject;
if (editableObject != null)
{
editableObject.CancelEdit();
}
}
foreach (PropertyInfo collProperty in editableProp.Collections)
{
IList collOld = collProperty.GetValue(_stub, null) as IList;
IList collNew = collProperty.GetValue(this, null) as IList;
//如果兩個集合不相同,那麼就恢復原始集合的引用。
if (!object.ReferenceEquals(collOld, collNew))
{
collProperty.SetValue(this, collOld, null);
}
//對原始集合中每個IeditableObject,遞迴呼叫CancelEdit
if (collOld != null)
{
foreach (IEditableObject editableObject in collOld.OfType<IEditableObject>())
{
editableObject.CancelEdit();
}
}
}
//清除副本
_stub = null;
}
public void EndEdit()
{
//如果沒有處於編輯狀態,就什麼也不做。
if (!IsEditing)
{
return;
}
IsEditing = false;
var editableProp = GetEditableProperty();
//對於每個IeditableObject屬性,遞迴呼叫EndEdit
foreach (PropertyInfo editableProperty in editableProp.Editables)
{
IEditableObject editableObject = editableProperty.GetValue(this, null) as tableObject;
if (editableObject != null)
{
editableObject.EndEdit();
}
}
//對於集合屬性中每個項,如果其是IeditableObject,則遞迴呼叫EndEdit
foreach (PropertyInfo collProperty in editableProp.Collections)
{
IList collNew = collProperty.GetValue(this, null) as IList;
if (collNew != null)
{
foreach (IEditableObject editableObject in collNew.OfType<IEditableObject>())
{
editableObject.EndEdit();
}
}
}
//清除副本
_stub = null;
}
private bool GetIsChanged(HashSet<EditableObject> markedObjects)
{
//如果沒有在編輯狀態,那麼表示物件沒有改變
if (!IsEditing)
{
return false;
}
//如果物件已經被檢查過了,說明出現迴圈引用,並且被檢查過的物件沒有改變。
if (markedObjects.Contains(this))
{
return false;
}
var editableProp = GetEditableProperty();
//檢測標量屬性有沒有變化。
foreach (PropertyInfo scalarProperty in editableProp.Scalars)
{
object newValue = scalarProperty.GetValue(this, null);
object oldValue = scalarProperty.GetValue(_stub, null);
bool changed = false;
if (newValue != null)
{
changed =!newValue.Equals(oldValue);
}
else if (oldValue != null)
{
changed = true;
}
if (changed)
{
return true;
}
}
//標記此物件已經被檢查過
markedObjects.Add(this);
//對於每一個IeditableObject屬性,進行遞迴檢查
foreach (PropertyInfo editableProperty in editableProp.Editables)
{
EditableObject editableObject = editableProperty.GetValue(this, null) as EditableObject;
if (editableObject != null)
{
if (editableObject.GetIsChanged(markedObjects))
{
return true;
}
}
}
//檢查集合物件的想等性
foreach (PropertyInfocollectionProperty in editableProp.Collections)
{
IList empty = new object[0];
IList collOld = (collectionProperty.GetValue(_stub, null) as IList) ?? empty;
IList collNew = (collectionProperty.GetValue(this, null) as IList) ?? empty;
if (!object.ReferenceEquals(collOld, collNew))
{
//Detectif elements are added or deleted in Collection.
if (!collOld.Cast<object>().SequenceEqual(collNew.Cast<object>()))
{
return true;
}
}
//Detectif any element is changed in collection.
foreach (var item in collNew)
{
EditableObject editableObject = item as EditableObject;
if (editableObject != null)
{
if (editableObject.GetIsChanged(markedObjects))
{
return true;
}
}
}
}
return false;
}
private EditableProperty GetEditableProperty()
{
return _cachedEditableProperties.GetOrAdd(GetType(), t => new EditableProperty(t));
}
}
在WPF程式裡面,大部分業務物件都要實現InotifyPropertyChanged以便資料繫結,所以我們實現了這個介面,並讓EditableObject從這個實現派生,從而讓Editableobject也具有繫結支援。NotifiableObject類非處簡單,如下:
[<