IEditableObject的一个通用实现

简介: 原文:IEditableObject的一个通用实现 IeditableObject是一个通用接口,用于支持对象编辑。当我们在界面上选择一个条目,然后对其进行编辑的时候,接下来会有两种操作,一个是保持编辑结果,一个取消编辑。
原文: IEditableObject的一个通用实现

IeditableObject是一个通用接口,用于支持对象编辑。当我们在界面上选择一个条目,然后对其进行编辑的时候,接下来会有两种操作,一个是保持编辑结果,一个取消编辑。这就要求我们保留原始值,否则我们只能到数据库里面再次查询。IeditableObject接口的三个方法定义为我们定义了这个行为规范:


    public interface IEditableObject


    {


        // 开始编辑,一般在此方法内创建当前对象副本


        void BeginEdit();


        //取消编辑,当副本恢复到当前对象,并清除副本


        void CancelEdit();


        // 接受编辑结果,并清除副本


        void EndEdit();


}


对于IeditableObject的实现,应该满足一下要求:


  1. 具有NonEditableAttribute标记的属性不参与编辑

  2. 如果某个属性类型也实现了IeditableObject 那么将递归调用相应编辑方法。

  3. 对于集合对象,如果集合对象实现了IeditableObject,将会对集合的每个项调用相应编辑方法。

  4. 可以查询对象是否改变,包括任何标量属性的变化,关联的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类非处简单,如下:


[Serializable]


    public abstract class NotifiableObject : INotifyPropertyChanged


    {


        private const string ERROR_MSG = "{0}is not a public property of {1}";


        private static readonly ConcurrentDictionary<string, PropertyChangedEventArgs> _eventArgCache;


 


        static NotifiableObject()


        {


    //缓存PropertyChangedEventArgs,以提高性能。


            _eventArgCache = new ConcurrentDictionary<string, PropertyChangedEventArgs>();


        }


 


        [field: NonSerialized]


        public event PropertyChangedEventHandler PropertyChanged;


 


        public static PropertyChangedEventArgs GetPropertyChangedEventArgs(string propertyName)


        {


            if (string.IsNullOrEmpty(propertyName))


            {


                throw new ArgumentException("propertyName cannotbe null or empty.");


            }


 


            return _eventArgCache.GetOrAdd(propertyName, p => new PropertyChangedEventArgs(p));


        }


 


        protected void OnPropertyChanged([CallerMemberName]string propertyName = "")


        {


            VerifyProperty(propertyName);


            PropertyChangedEventHandler handler = PropertyChanged;


 


            if (handler != null)


            {


                var args = GetPropertyChangedEventArgs(propertyName);


                handler(this, args);


            }


        }


 


        [Conditional("DEBUG")]


        private void VerifyProperty(string propertyName)


        {


            Type type = GetType();


            PropertyInfo propInfo = type.GetProperty(propertyName);


 


            if (propInfo == null)


            {


                Debug.Fail(string.Format(ERROR_MSG, propertyName, type.FullName));


            }


        }


}


下面的单元测试代码对EditableObject进行的简单的测试:


[TestClass]


    public class EditableObjectTest


    {


        [TestMethod]


        public void UseEditableObject_WithoutCallingMethodsOfIEditableObject()


        {


            User u = new User() { Name = "john", Age = 20, Wage = 200 };


 


            Assert.AreEqual(u.Name, "john");


            Assert.AreEqual(u.Age, 20);


            Assert.AreEqual(u.Wage, 200);


            Assert.IsFalse(u.IsChanged);


            Assert.IsFalse(u.IsEditing);


 


            u.Age = 21;


            u.Wage = 250;


            Assert.AreEqual(u.Name, "john");


            Assert.AreEqual(u.Age, 21);


            Assert.AreEqual(u.Wage, 250);


        }


 


        [TestMethod]


        public void BeginEdit_EndEdit_ScalarProperties()


        {


            User u = new User() { Name = "john", Age = 20, Wage = 200 };


 


            u.BeginEdit();


            Assert.IsFalse(u.IsChanged);


            Assert.IsTrue(u.IsEditing);


 


            u.Age = 21;


            Assert.IsTrue(u.IsChanged);


            Assert.IsTrue(u.IsEditing);


 


            u.EndEdit();


            Assert.AreEqual(u.Age, 21);


            Assert.IsFalse(u.IsEditing);


            Assert.IsFalse(u.IsChanged);


        }


 


        [TestMethod]


        public void BeginEdit_CancelEdit_ScalarProperties()


        {


            User u = new User() { Name = "john", Age = 20, Wage = 200 };


            u.BeginEdit();


            u.Wage = 250;


            u.CancelEdit();


            Assert.IsFalse(u.IsEditing);


            Assert.IsFalse(u.IsChanged);


            Assert.AreEqual(u.Wage, 200);


        }


 


        [TestMethod]


        public void BeginEdit_EndEdit_EditableProperties()


        {


            DateTime start = new DateTime(2000, 1, 1);


            DateTime newStart = new DateTime(2000, 1, 2);


 


            User u = new User() { Name = "john", Age = 20, Wage = 200 };


            Task t = new Task() { Name = "writereports", StartTime = start, Owner =u };


            u.Task = t;


 


            u.BeginEdit();


            Assert.IsTrue(u.IsEditing);


            Assert.IsFalse(u.IsChanged);


            Assert.IsTrue(t.IsEditing);


            Assert.IsFalse(t.IsChanged);


 


            t.StartTime = newStart;


            Assert.IsTrue(u.IsEditing);


            Assert.IsTrue(u.IsChanged);


            Assert.IsTrue(t.IsEditing);


            Assert.IsTrue(t.IsChanged);


 


            u.EndEdit();


            Assert.AreEqual(t.StartTime, newStart);


            Assert.IsFalse(u.IsEditing);


            Assert.IsFalse(u.IsChanged);


            Assert.IsFalse(t.IsEditing);


            Assert.IsFalse(t.IsChanged);


        }


 


        [TestMethod]


        public void BeginEdit_CancelEdit_EditableProperties()


        {


            DateTime start = new DateTime(2000, 1, 1);


            DateTime newStart = new DateTime(2000, 1, 2);


 


            User u = new User() { Name = "john", Age = 20, Wage = 200 };


            Task t = new Task() { Name = "writereports", StartTime = start, Owner =u };


            u.Task = t;


            u.BeginEdit();


            t.StartTime = newStart;


 


            u.CancelEdit();


            Assert.AreEqual(t.StartTime, start);


            Assert.IsFalse(u.IsEditing);


            Assert.IsFalse(u.IsChanged);


            Assert.IsFalse(t.IsEditing);


            Assert.IsFalse(t.IsChanged);


        }


 


        [TestMethod]


        public void BeginEdit_EndEdit_CollectionProperties_WithModify()


        {


            User u = new User() { Name = "john", Age = 20, Wage = 200 };


            var item = new SettingItem { Name = "setting1", Value = "10" };


            u.Settings = new List<SettingItem>();


            u.Settings.Add(item);


 


            u.BeginEdit();


            Assert.IsTrue(u.IsEditing);


            Assert.IsFalse(u.IsChanged);


            Assert.IsTrue(item.IsEditing);


            Assert.IsFalse(item.IsChanged);


 


            item.Value = "20";


            Assert.IsTrue(u.IsEditing);


            Assert.IsTrue(u.IsChanged);


            Assert.IsTrue(item.IsEditing);


            Assert.IsTrue(item.IsChanged);


 


            u.EndEdit();


            Assert.AreEqual(item.Value, "20");


            Assert.IsFalse(u.IsEditing);


            Assert.IsFalse(u.IsChanged);


            Assert.IsFalse(item.IsEditing);


            Assert.IsFalse(item.IsChanged);


        }


 


        [TestMethod]


        public void BeginEdit_CancelEdit_CollectionProperties_WithModify()


        {


            User u = new User() { Name = "john", Age = 20, Wage = 200 };


            var item = new SettingItem { Name = "setting1", Value = "10" };


            u.Settings = new List<SettingItem>();


            u.Settings.Add(item);


 


            u.BeginEdit();


            item.Value = "21";


            u.CancelEdit();


            Assert.AreEqual(item.Value, "10");


            Assert.IsFalse(u.IsEditing);


            Assert.IsFalse(u.IsChanged);


            Assert.IsFalse(item.IsEditing);


            Assert.IsFalse(item.IsChanged);


        }


 


        [TestMethod]


        public void BeginEdit_EndEdit_CollectionProperties_WithAdd()


        {


            User u = new User() { Name = "john", Age = 20, Wage = 200 };


            u.Settings = new List<SettingItem>();


 


            u.BeginEdit();


            var item = new SettingItem { Name = "setting1", Value = "10" };


            u.Settings.Add(item);


 


            Assert.IsTrue(u.IsEditing);


            Assert.IsTrue(u.IsChanged);


            Assert.IsFalse(item.IsEditing);


            Assert.IsFalse(item.IsChanged);


 


            u.EndEdit();


            Assert.AreEqual(u.Settings[0].Value, "10");


            Assert.IsFalse(u.IsEditing);


            Assert.IsFalse(u.IsChanged);


            Assert.IsFalse(item.IsEditing);


            Assert.IsFalse(item.IsChanged);


        }


 


        [TestMethod]


        public void BeginEdit_EndEdit_CollectionProperties_WithDelete()


        {


            User u = new User() { Name = "john", Age = 20, Wage = 200 };


            var item = new SettingItem { Name = "setting1", Value = "10" };


            u.Settings = new List<SettingItem>();


            u.Settings.Add(item);


 


            u.BeginEdit();


            u.Settings.Clear();


 


            Assert.IsTrue(u.IsEditing);


            Assert.IsTrue(u.IsChanged);


 


            u.EndEdit();


 


            Assert.AreEqual(u.Settings.Count, 0);


            Assert.IsFalse(u.IsEditing);


            Assert.IsFalse(u.IsChanged);


        }


    }


 


    class User : EditableObject, ICloneable


    {


        public string Name { get; set; }


        public decimal Wage { get; set; }


        public int Age { get; set; }


 


        public Task Task { get; set; }


        public List<SettingItem> Settings { get; set; }


 


        public object Clone()


        {


            User u = MemberwiseClone() as User;


            if (Settings != null)


            {


                u.Settings = new List<SettingItem>(Settings);


            }


            return u;


        }


    }


 


    class Task : EditableObject


    {


        public string Name { get; set; }


        public DateTime StartTime { get; set; }


        public User Owner { get; set; }


    }


 


    class SettingItem : EditableObject


    {


        public string Name { get; set; }


        public string Value { get; set; }


    }



目录
相关文章
|
7月前
5.3.2.7 通用操作
5.3.2.7 通用操作
30 0
|
3月前
|
机器学习/深度学习 搜索推荐 算法
直接调用通用大模型开发应用与基于开源大模型“自研”两种方式比较
【1月更文挑战第23天】直接调用通用大模型开发应用与基于开源大模型“自研”两种方式比较
49 1
直接调用通用大模型开发应用与基于开源大模型“自研”两种方式比较
|
7月前
|
算法 编译器 C++
如何编写一个通用的函数?
如何编写一个通用的函数?
65 2
|
7月前
快速生成通用接口业务配置
快速生成通用接口业务配置
|
8月前
|
弹性计算 缓存 网络协议
测试:阿里云U1实例(通用算力型实例)服务器详细介绍
性能测试:阿里云U1实例(通用算力型实例)服务器详细介绍
222 0
|
9月前
|
SQL 消息中间件 缓存
12种接口优化的通用方案
12种接口优化的通用方案
145 0
|
存储 XML 编译器
【C#基础】C# 程序通用结构
编程语言C# 程序结构的介绍 。
175 0
【C#基础】C# 程序通用结构
|
缓存
标准 I/O 的核心操作
标准 I/O 的核心操作
59 0
|
Web App开发 前端开发 JavaScript
我觉得网站的通用部分
这个图片是我用xmind编辑的。欢迎大家来纠正或添加! 再来说说我对网站开发的理解与碰到的问题。
我觉得网站的通用部分