IEnumerable >結構解析通用解決方案(支持指定屬性順序)


一、前言

類似如下字符串

 "ID", "NameValue", "CodeValue", "ExchangeTypeValue", 6, "invalid"

 "ID2", "NameValue2", "CodeValue2", "ExchangeTypeValue2", 6, "invalid"

.......

有可能是文件中存在的,或者調用其他程序返回的結構化數據,那么該如何解析?當其他場景中,只是返回順序(屬性順序)變了,類結構還是一樣,又如何應對?當有很多類似場景時,是不是該抽象出一個泛型方法來應對該場景?當然,也不僅僅於上述情況,可能返回的結構是確定,只是形式不一樣,這個過程這里暫時省略,因為正則表達式完全能夠解析出來。要用以下的方法,必須轉換成IEnumerable<IEnumerable<string>>結構,IEnumerable<IEnumerable<string>>結構中IEnumerable<string>為一個對象所有的值,總體是多個對象的值集合。本文中用反射寫的(關於IL操作的后續文章提供),相關的類圖如下:

二、ResultTransfer的具體實現

ResultTransfer主要用於對IEnumerable<IEnumerable<string>>結構的解析,另外還可以指定params string[] propertyNames屬性參數列表來確定解析順序(也即是屬性順序),主要方法如下:

     public static IList<T> Parse<T>(IEnumerable<IEnumerable<string>> entityRows, params string[] propertyNames) where T : new()

     第一個參數entityRows為對象列表值集合。

     第二個參數propertyNames為可選參數,輸入該參數后,如果propertyNames中存在相關屬性,則按照propertyNames對應的屬性順序進行解析。否則按照提供的T類中屬性的DataMemberAttribute來確定屬性順序進行解析。

實現代碼非常簡潔和簡單,方法具體如下所示:

        public static IList<T> Parse<T>(IEnumerable<IEnumerable<string>> entityRows, params string[] propertyNames) where T : new()
        {
            if (entityRows == null || entityRows.Count() == 0)
            {
                return new List<T>();
            }

            IList<T> entities = new List<T>();
            var members = new DataMemberAttributeCollection(typeof(T), propertyNames);

            if (members.Count <= 1)
            {
                return new List<T>();
            }

            FuncProvider funcProvider = new FuncProvider();

            foreach (var propertyValues in entityRows)
            {
                if (propertyValues == null || propertyValues.Count() == 0)
                {
                    continue;
                }

                entities.Add(Generate<T>(propertyValues, members, funcProvider));
            }

            return entities;
        }

        private static T Generate<T>(IEnumerable<string> propertyValues, DataMemberAttributeCollection members,
            FuncProvider funcProvider) where T : new()
        {
            T entity = Activator.CreateInstance<T>();
            int memberCount = members.Count;
            int propertyCount = propertyValues.Count();

            if (memberCount == 0 || propertyCount == 0)
            {
                return entity;
            }

            int convertCount = Math.Min(memberCount, propertyCount);
            DataMemberAttribute currAttribute;
            PropertyInfo currPropertyInfo;

            int propertyValueIndex = 0;

            foreach (string propertyValue in propertyValues)
            {
                if (propertyValueIndex >= convertCount)
                {
                    break;
                }

                propertyValueIndex++;
                currAttribute = members[propertyValueIndex - 1];
                currPropertyInfo = currAttribute.PropertyInfo;

                if (propertyValue == null)
                {
                    currPropertyInfo.SetValue(entity, null, null);
                    continue;
                }

                if (propertyValue.GetType() == currAttribute.PropertyType)
                {
                    currPropertyInfo.SetValue(entity, propertyValue, null);
                }
                else
                {
                    object result = funcProvider.DynamicInvoke(currAttribute.PropertyType, (propertyValue ?? string.Empty).ToString());
                    currPropertyInfo.SetValue(entity, result, null);
                }
            }

            return entity;
        }

三、DataMemberAttributeCollection的具體實現

DataMemberAttributeCollection集合類主要用於設置解析屬性的順序,同樣,該類提供二個參數的構造函數用於生成相應的配置信息public DataMemberAttributeCollection(Type type, params string[] propertyNames)。

主要代碼如下:

        public void GetConfiguration(Type type, params string[] propertyNames)
        {
            if (type == null || type.GetProperties().Length <= 0)
            {
                return;
            }

            if (propertyNames == null || propertyNames.Length == 0)
            {
                AddAllDataMemberAttributes(type);
            }
            else
            {
                AddDataMemberAttributes(type, propertyNames);
            }

            this._memberAttributes = this._memberAttributes.OrderBy(p => p.Order).ToList();
        }

        private void AddDataMemberAttributes(Type type, string[] propertyNames)
        {
            IList<PropertyInfo> validPropertyInfos = new List<PropertyInfo>();
            PropertyInfo tempPropertyInfo;

            foreach (string propertyName in propertyNames)
            {
                if (string.IsNullOrWhiteSpace(propertyName))
                {
                    continue;
                }

                tempPropertyInfo = type.GetProperty(propertyName.Trim());

                if (tempPropertyInfo == null)
                {
                    throw new ArgumentException(string.Format(@"Contains Invalid Property Name Arg : {0}.", propertyName.Trim()));
                }

                validPropertyInfos.Add(tempPropertyInfo);
            }

            if (validPropertyInfos.Count() > 0)
            {
                foreach (var property in validPropertyInfos)
                {
                    AddAttributes(new DataMemberAttribute(), property);
                }
            }
        }

        private void AddAllDataMemberAttributes(Type type)
        {
            DataMemberAttribute attr = null;
            foreach (PropertyInfo propertyInfo in type.GetProperties())
            {
                attr = AttributeUtility.GetCustomAttribute<DataMemberAttribute>(propertyInfo);

                if (attr == null)
                {
                    continue;
                }

                if (!attr.IsRequire)
                {
                    continue;
                }

                if (this._memberAttributes.Count(p => p.Order == attr.Order) > 0)
                {
                    throw new ArgumentException(string.Format(@"Contains Same Order {0}.Please Look Up DataMemberAttribute
                            Of The Type {1}", attr.Order, type.Name));
                }

                AddAttributes(attr, propertyInfo);
            }
        }

        private void AddAttributes(DataMemberAttribute attr, PropertyInfo propertyInfo)
        {
            if (string.IsNullOrWhiteSpace(attr.Name))
            {
                attr.Name = propertyInfo.Name;
            }

            attr.PropertyName = propertyInfo.Name;
            attr.PropertyType = propertyInfo.PropertyType;
            attr.PropertyInfo = propertyInfo;

            this._memberAttributes.Add(attr);
        }

該類確保指定Type的類中DataMemberAttribute是否設置正確(是否有相同的Order),確保是否輸入了錯誤的propertyName。

四、具體應用

對於具體應用的話,用單元測試來得方便與直接。

(1)對於只輸入一個參數的應用如下:

        [TestMethod()]
        public void ParseTest()
        {
            IList<IList<string>> entityRows = new List<IList<string>>();
            entityRows.Add(new List<string>() { "3", "NameValue", "CodeValue", "ExchangeTypeValue", "6", "invalid" });

            var contracts = ResultTransfer.Parse<ContinousContract>(entityRows);

            Assert.IsNotNull(contracts);
            Assert.IsTrue(contracts.Count == 1);
            Assert.AreEqual(contracts[0].Code, "CodeValue");
            Assert.AreEqual(contracts[0].Name, "NameValue");
            Assert.AreEqual(contracts[0].ExchangeType, "ExchangeTypeValue");
            Assert.AreEqual(contracts[0].OrgidID, 3);
            Assert.AreEqual(contracts[0].ExchangeTypeValue, 6);
        }

(2)對於只輸入無效參數的應用如下:

        [TestMethod()]
        public void ParseWithInvalidArgTest()
        {
            IList<IList<string>> entityRows = new List<IList<string>>();
            entityRows.Add(new List<string>() { "sss", "NameValue", "CodeValue", "ExchangeTypeValue", "6", "invalid" });

            var contracts = ResultTransfer.Parse<ContinousContract>(entityRows);

            Assert.IsNotNull(contracts);
            Assert.IsTrue(contracts.Count == 1);
            Assert.AreEqual(contracts[0].Code, "CodeValue");
            Assert.AreEqual(contracts[0].Name, "NameValue");
            Assert.AreEqual(contracts[0].ExchangeType, "ExchangeTypeValue");
            Assert.AreEqual(contracts[0].OrgidID, 0);
            Assert.AreEqual(contracts[0].ExchangeTypeValue, 6);
        }

輸入無效的IEnumerable<IEnumerable<string>>參數,方法內部會進行隱式的轉換,比如“sss”轉換成0。

(3)對於二個參數時的應用如下:

        [TestMethod()]
        public void ParseWithArgTest()
        {
            IList<IList<string>> entityRows = new List<IList<string>>();
            entityRows.Add(new List<string>() { "3", "NameValue", "ExchangeTypeValue", "6", "invalid" });
            var propertyNames = new List<string>() { "ExchangeTypeValue", "Name", "", "ExchangeType" };

            var contracts = ResultTransfer.Parse<ContinousContract>(entityRows, propertyNames.ToArray());

            Assert.IsNotNull(contracts);
            Assert.IsTrue(contracts.Count == 1);
            Assert.AreEqual(contracts[0].Code, null);
            Assert.AreEqual(contracts[0].Name, "NameValue");
            Assert.AreEqual(contracts[0].ExchangeType, "ExchangeTypeValue");
            Assert.AreEqual(contracts[0].OrgidID, 0);
            Assert.AreEqual(contracts[0].ExchangeTypeValue, 3);
        }

一旦輸入二個參數,且propertyNames參數的個數大於0,則以propertyNames對應的屬性順序進行解析。對於輸入錯誤的屬性名,方法內部會拋出異常,當然也可以增加一個參數用於控制是否拋出異常,或者寫入日志文件中等。

對於將固定格式的字符串解析成IEnumerable<IEnumerable<string>>,正則表達式解析的話比較簡單,此文不做講解,略過...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM