前言
最近接手一个winform端的项目,第一次真切体验到,代码会乱和需求不确定,频繁修改的无奈。之前工作经验中项目都会分三层,让我以为所有项目都会这样。而我的接受的项目确是所有代码都写在窗体代码中,不管时数据访问,还是业务处理,都和窗体事件和相关代码混在一起。当开发其他页面时,又要重新开发,没有复用可言。而且一修改,每个位置都要一一找出来进行修改。而且基本没有模型类,原始数据直接用数据表加下标,以及数组进行处理。例如:改DataGridView时,每一次调整列位置,都要修改相应数据赋值(赋值时按下标赋值,列一多,对应容易出错),如果改为对象,则调整列下标时,可不再关注赋值这块修改。
数据库使用SQLite,这个项目有使用[System.Data.SQLite]查出数据集获取值,有使用NPoco微ORM框架,由于这个项目已经欢乐3、4个人了,可能中间有人换上NPoco。由于之使用EF的对象关系映射[ORM]时,需求基本都是确定了,所以对[ORM]框架体验不深。而这次由于数据字段都需要频繁修改,感觉是实在太方便了,只需改变下特性和模型就可以了,不再需要在数据访问处到处修改(之前数据访问还没有单独分出一层…)。
反射、特性、泛型,可以写出复用性高代码,但会降低代码的可读性,不利于新接手人快速熟悉项目代码。之前曾利用自定义特性设置打印字段和日志字段,和快速将枚举转成下拉框数据源。利用这技术可以将 表数据转成实体数据。
反射 runoob菜鸟编程 微软-编程概念
用途
提供描述程序集、模块和类型的对象(Type 类型)。
动态地创建类型的实例,将类型绑定到现有对象。
从现有对象中获取类型,然后调用其方法或访问器字段和属性。
利用反射来访问特性
优点
1、反射提高了程序的范围和扩展性。
2、降低聚合性,提高效率。
3、它允许程序创建和控制任何类的对象,不需要提前硬编码目标类
缺点
1、性能问题:使用反射最接近的一种,用于现场和方法的解释操作要远慢于直接代码。因此,机制主要在对反射和可能需要的系统框架上,普通程序不建议使用。
2、反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射代码中使用了源代码的技术,可能会带来维护的问题,反射代码比相应的直接代码更复杂。
特性 runoob菜鸟编程 微软-利用特性扩展元数据
公共语言运行时(CLR,所有.NET应用程序运行时环境,所有.NET应用程序都使用的编程基础) 能够添加类似于关键字的描述性声明(称为特性),以便批注编程元素(如类型、字段、方法和属性)。
编译 运行时的代码时,它将被转换为 Microsoft 中间语言 (MSIL),并和编译器生成的元数据一起放置在可移植可执行 (PE) 文件内。
特性能够将额外的描述性信息放到可使用运行时反射服务提取的元数据中。
当声明派生自 System.Attribute 的特殊类的实例时,编译器会创建特性
自定义特性
新增一个类,然后该类集成Attribute即可
using System;
namespace LchCommon
{
/// <summary>
/// 自定义打印特性
/// AllowMultiple:是否可以为一个程序元素,指定多个所指示特性
/// Inherited:是否特性由派生类和重写成员继承
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field,
AllowMultiple = true,Inherited = false)]
public class Print:Attribute
{
//=================== construct function ==================
#region construct 属性
/// <summary>
/// 属性
/// </summary>
public Print(string name,string desc = null)
{
this.m_name = name;
this.m_desc = desc;
}
#endregion
//=================== private fields ======================
#region fields
private string m_name;
private string m_desc;
#endregion
//=================== public properties ===================
#region Name 名称
/// <summary>
/// 名称
/// </summary>
public string Name => this.m_name;
#endregion
#region Desc 描述
/// <summary>
/// 描述
/// </summary>
public string Desc => this.m_desc;
#endregion
}
}
额外用到的枚举和类
用于存反射属性结果
namespace LchCommon
{
public class Property
{
//=================== construct function ==================
#region construct 用于存反射属性结果
/// <summary>
/// 用于存反射属性结果
/// </summary>
public Property()
{
}
#endregion
//=================== private fields ======================
#region fields
private string m_name;
private string m_value;
private string m_text;
#endregion
//=================== public properties ===================
#region Name 名称
/// <summary>
/// 名称
/// </summary>
public string Name
{
get => this.m_name;
set => this.m_name = value;
}
#endregion
#region value 值
/// <summary>
/// 值
/// </summary>
public string Value
{
get => this.m_value;
set => this.m_value = value;
}
#endregion
#region text 文本
/// <summary>
/// 文本
/// </summary>
public string Text
{
get => this.m_text;
set => this.m_text = value;
}
#endregion
}
}
测试枚举
#region ExcelType Excel类型
/// <summary>
/// Excel类型
/// </summary>
public enum ExcelType
{
#region xls 2003
/// <summary>
/// xls 2003
/// </summary>
[Print(".xls文件格式")]
XLS = 1 << 0,
#endregion
#region xlsx 2007
/// <summary>
/// xls 2003
/// </summary>
[Print(".xlsx文件格式")]
XLSX = 1 << 1,
#endregion
}
#endregion
反射帮助类ReflectionHelper
using System;
using System.Collections.Generic;
using System.Reflection;
namespace LchCommon.Helper
{
public class ReflectionHelper
{
//=================== construct function ==================
#region construct 反射帮助类
/// <summary>
/// 反射帮助类
/// </summary>
public ReflectionHelper()
{
}
#endregion
//=================== public metod ========================
/* enum */
#region GetPropertyList_Enum 获取枚举类型的Print特性
/// <summary>
/// 获取枚举类型的Print特性
/// </summary>
/// <param name="type"></param>
/// <returns>可用于下拉框的数据源</returns>
public static List<Property> GetPropertyList_Enum(Type type)
{
var _lst = new List<Property>();
//判断类型是否为枚举
if (!type.IsEnum)
{
return _lst;
}
Print[] _attrs;//自定义特性
string _attrName;//自定义特性设置的名称
FieldInfo _fieldType;//字段类型
var _valueArr = Enum.GetValues(type);//枚举的中所有值
foreach (var item in _valueArr)
{
_fieldType = type.GetField(item.ToString());//搜索具有指定名称的公共字段
if(_fieldType==null)
{
continue;
}
_attrs = _fieldType.GetCustomAttributes(typeof(Print),false) as Print[];//获取自定义特性
if (_attrs == null || _attrs.Length == 0)
{
continue;
}
_attrName = _attrs[0].Name;
if (!string.IsNullOrEmpty(_attrName))
{
_lst.Add(new Property
{
Name = item.ToString(),
Value = ((int)item).ToString(),
Text = _attrName,
});
}
}
return _lst;
}
#endregion
/* class */
#region GetPropertyList_Class 获取class类型的Print特性
/// <summary>
/// 获取class类型的Print特性
/// </summary>
/// <param name="type"></param>
/// <returns>可用于下拉框的数据源</returns>
public static List<Property> GetPropertyList_Class(Type _type)
{
var _lst = new List<Property>();
if (!_type.IsClass)
{
return _lst;
}
var _propArr = _type.GetProperties();
string _value;
foreach (var _prop in _propArr)
{
var attrs = _prop.GetCustomAttributes(typeof(Print),false) as Print[];
if (attrs == null || attrs.Length == 0)
{
continue;
}
_value = attrs[0].Name;
if (_prop.IsDefined(typeof(Print),false))
{
_lst.Add(new Property
{
Name = _prop.Name,
Value = _value,
Text = _value,
});
}
}
return _lst;
}
#endregion
#region GetPropertyList_Object 获取对象的Print特性
/// <summary>
/// 获取对象的Print特性
/// </summary>
/// <param name="data">对象</param>
/// <returns>可用于下拉框的数据源</returns>
public static List<Property> GetPropertyList_Object(object data)
{
var _lst = new List<Property>();
var _type = data.GetType();
if (!_type.IsClass)
{
return _lst;
}
string _fileValue;
var _propArr = _type.GetProperties();
string _value;
Print[] _attrs;
foreach (var _prop in _propArr)
{
_attrs = _prop.GetCustomAttributes(typeof(Print),false) as Print[];//获取自定义特性
if (_attrs == null || _attrs.Length == 0)
{
continue;
}
_value = _attrs[0].Name;//设置的Print特性的名称
if (string.IsNullOrEmpty(_value))
{
_value = _prop.Name;
}
_fileValue = GetValue(data,_prop);//取值
_lst.Add(new Property
{
Name = _prop.Name,
Value = _fileValue,
Text = _value,
});
}
return _lst;
}
#endregion
#region SetValue 给对象赋值
/// <summary>
/// 给对象赋值
/// </summary>
/// <typeparam name="T">泛型对象</typeparam>
/// <param name="source">数据源</param>
/// <param name="data">对象实例</param>
/// <param name="defaultCol">当没有特性时,查找默认列,按列下表赋值</param>
/// <returns>是否成功</returns>
public static bool FillData<T>(List<Property> source,T data,string defaultCol = "column") where T : new()
{
var _type = data.GetType();
if (!_type.IsClass || source == null || source.Count == 0)
{
return false;
}
var _propArr = _type.GetProperties();
Property _param;
PropertyInfo _prop;
string _col;
var _result = true;
for (var i = 0;i < _propArr.Length;i++)
{
_param = null;
_prop = _propArr[i];
var attrs = _prop.GetCustomAttributes(typeof(Print),false) as Print[];
if (attrs != null && attrs.Length > 0)
{
_col = attrs[0].Name;
_param = source.Find(f => f.Name == _col);
}
//查找并填充值
if (_param == null)
{
_param = source.Find(f => f.Name == defaultCol + i.ToString());
}
if (_param == null)
{
continue;
}
try
{
/*
_prop.SetValue(data,_param.Value,null);//此语句有问题
当_prop的属性类性为float时,会抛出下面异常,类型“System.String”的对象无法转换为类型“System.Single”。
即SetValue方法不会帮我们强转_param.Value的类型,需要我们自己转换。
*/
//下面是使用this关键字对字符串进行的扩展方法。
_prop.SetValue(data,_param.SysValue.ToTypeValue(_prop.PropertyType),null);
}
catch
{
_result = false;
}
}
return _result;
}
#endregion
//=================== private metod =======================
#region GetValue 获取值
/// <summary>
/// 获取值
/// </summary>
/// <param name="data">对象</param>
/// <param name="_prop">属性信息</param>
/// <returns>对象对应属性值</returns>
private static string GetValue(object data,PropertyInfo _prop)
{
var _fileValue = _prop.GetValue(data,null);
if (_fileValue == null)
{
return "";
}
else
{
switch (_prop.PropertyType.Name.ToLower())
{
case "datatime":
return ((DateTime)_fileValue).ToString("yyyy-MM-dd HH:mm:ss");//yyyy-MM-dd HH:mm:ss.fff
case "float":
case "double":
return ((float)_fileValue).ToString("0.0");
default:
return _fileValue.ToString();
}
}
}
#endregion
}
}
字符串的扩展方法 StringExtend
using System;
namespace LchCommon
{
public static class StringExtend
{
/*======================= method =================================*/
/* 辅助 */
#region ToInt 字符串转Int
/// <summary>
/// 字符串转Int
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static int ToInt(this string data)
{
if (int.TryParse(data,out var result))
{
return result;
}
else if (float.TryParse(data,out var resultf))
{
return (int)resultf;
}
return 0;
}
#endregion
#region ToFloat 字符串转Float
/// <summary>
/// 字符串转Float
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static float ToFloat(this string data)
{
if (float.TryParse(data,out var resultf))
{
return resultf;
}
return 0;
}
#endregion
#region ToTypeValue 字符串转指定类型
/// <summary>
/// 字符串转指定类型
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static object ToTypeValue(this string data,Type type)
{
if (!type.IsValueType)
{
return data;
}
switch (type.Name.ToLower())
{
case "single":
case "float":
return data.ToFloat();
case "double":
if (!double.TryParse(data,out Double doubleRes))
{
doubleRes = 0;
}
return doubleRes;
case "decimal":
if (!decimal.TryParse(data,out Decimal decimalRes))
{
decimalRes = 0;
}
return decimalRes;
case "int16":
case "short":
if (!short.TryParse(data,out Int16 int16Res))
{
int16Res = 0;
}
return int16Res;
case "int32":
case "int":
if (!int.TryParse(data,out Int32 int32Res))
{
int32Res = 0;
}
return int32Res;
case "int64":
case "long":
if (!long.TryParse(data,out Int64 int64Res))
{
int64Res = 0;
}
return int64Res;
case "string":
return data;
default:
break;
}
return data;
}
#endregion
}
}
运行结果
枚举值GetPropertyList_Enum
类GetPropertyList_Class
namespace LchCommon
{
public class TestClass
{
#region Name 名称
/// <summary>
/// 名称
/// </summary>
[Print("名称列")]
public string Name { get; set; }
#endregion
#region Desc 描述
/// <summary>
/// 描述
/// </summary>
[Print("描述列")]
public string Desc { get; set; }
#endregion
}
}
对象GetPropertyList_Object