OSharp是什么?
OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现。与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现。依赖注入、ORM、对象映射、日志、缓存等等功能,都只定义了一套最基础最通用的抽象封装,提供了一套统一的API、约定与规则,并定义了部分执行流程,主要是让项目在一定的规范下进行开发。所有的功能实现端,都是通过现有的成熟的第三方组件来实现的,除了EntityFramework之外,所有的第三方实现都可以轻松的替换成另一种第三方实现,OSharp框架正是要起隔离作用,保证这种变更不会对业务代码造成影响,使用统一的API来进行业务实现,解除与第三方实现的耦合,保持业务代码的规范与稳定。
本文已同步到系列目录:OSharp快速开发框架解说系列
扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。 对于用 C# 和 Visual Basic 编写的客户端代码,调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异。
最常见的扩展方法是 LINQ 标准查询运算符,它将查询功能添加到现有的 System.Collections.IEnumerable 和 System.Collections.Generic.IEnumerable<T> 类型。 若要使用标准查询运算符,请先使用 using System.Linq 指令将它们置于范围中。 然后,任何实现了IEnumerable<T> 的类型看起来都具有 GroupBy、OrderBy、Average 等实例方法。 在 IEnumerable<T> 类型的实例(如 List<T> 或 Array)后键入“dot”时,可以在 IntelliSense 语句完成中看到这些附加方法。
以上摘自MSDN的扩展方法描述,可见扩展方法是个很实用的语法糖,用过都说好。
OSharp 框架中也定义(收集)了不少非常实用的扩展方法,位于工具组件OSharp.Utility.dll中,命名空间为OSharp.Utility.Extensions,下面我们来一一分解。
注:本篇都是非常基础的代码,代码的功能都有注释,可讲的东西不多,思想性的东西就更少了,不想看代码的可以跳过本篇。
声明:本文的扩展方法部分出自原创,部分收集整理于互联网(出处已不可考),冒犯之处望见谅。
代码总览:
泛型扩展,如果没有类型限定,基本上就是 Object 类型的扩展。因为所有类型都继承自Object类型的,如果给Objext类型定义扩展方法,那么所有的类型都会增加这个方法,有时候反而会阻碍编码流畅性。
在编码的过程中,经常会遇到数据类型转换的需求,为了方便统一,我们可以定义一个通用的类型转换扩展方法。
1 /// <summary> 2 /// 把对象类型转换为指定类型 3 /// </summary> 4 /// <param name="value"></param> 5 /// <param name="conversionType"></param> 6 /// <returns></returns> 7 public static object CastTo(this object value, Type conversionType) 8 { 9 if (value == null) 10 { 11 return null; 12 } 13 if (conversionType.IsNullableType()) 14 { 15 conversionType = conversionType.GetUnNullableType(); 16 } 17 if (conversionType.IsEnum) 18 { 19 return Enum.Parse(conversionType, value.ToString()); 20 } 21 if (conversionType == typeof(Guid)) 22 { 23 return Guid.Parse(value.ToString()); 24 } 25 return Convert.ChangeType(value, conversionType); 26 } 27 28 /// <summary> 29 /// 把对象类型转化为指定类型 30 /// </summary> 31 /// <typeparam name="T"> 动态类型 </typeparam> 32 /// <param name="value"> 要转化的源对象 </param> 33 /// <returns> 转化后的指定类型的对象,转化失败引发异常。 </returns> 34 public static T CastTo<T>(this object value) 35 { 36 object result = CastTo(value, typeof(T)); 37 return (T)result; 38 } 39 40 /// <summary> 41 /// 把对象类型转化为指定类型,转化失败时返回指定的默认值 42 /// </summary> 43 /// <typeparam name="T"> 动态类型 </typeparam> 44 /// <param name="value"> 要转化的源对象 </param> 45 /// <param name="defaultValue"> 转化失败返回的指定默认值 </param> 46 /// <returns> 转化后的指定类型对象,转化失败时返回指定的默认值 </returns> 47 public static T CastTo<T>(this object value, T defaultValue) 48 { 49 try 50 { 51 return CastTo<T>(value); 52 } 53 catch (Exception) 54 { 55 return defaultValue; 56 } 57 }
使用示例:
1 Assert.AreEqual(((object)null).CastTo<object>(), null); 2 Assert.AreEqual("123".CastTo<int>(), 123); 3 Assert.AreEqual(123.CastTo<string>(), "123"); 4 Assert.AreEqual(true.CastTo<string>(), "True"); 5 Assert.AreEqual("true".CastTo<bool>(), true); 6 Assert.AreEqual("56D768A3-3D74-43B4-BD7B-2871D675CC4B".CastTo<Guid>(), new Guid("56D768A3-3D74-43B4-BD7B-2871D675CC4B")); 7 Assert.AreEqual(1.CastTo<UriKind>(), UriKind.Absolute); 8 Assert.AreEqual("RelativeOrAbsolute".CastTo<UriKind>(), UriKind.RelativeOrAbsolute); 9 Assert.AreEqual("abc".CastTo<int>(123), 123); 10 ExceptionAssert.IsException<FormatException>(() => "abc".CastTo<int>());
很多时候都要把对象序列化成JSON字符串,又不想让项目到处依赖于JSON.NET这个第三方类库,定义一个JSON序列化的扩展方法,让工具组件来引用JSON.NET做这件事吧
1 /// <summary> 2 /// 将对象序列化为JSON字符串,不支持存在循环引用的对象 3 /// </summary> 4 /// <typeparam name="T">动态类型</typeparam> 5 /// <param name="value">动态类型对象</param> 6 /// <returns>JSON字符串</returns> 7 public static string ToJsonString<T>(this T value) 8 { 9 return JsonConvert.SerializeObject(value); 10 }
有来有往,JSON反序列化:
1 /// <summary> 2 /// 将JSON字符串还原为对象 3 /// </summary> 4 /// <typeparam name="T">要转换的目标类型</typeparam> 5 /// <param name="json">JSON字符串 </param> 6 /// <returns></returns> 7 public static T FromJsonString<T>(this string json) 8 { 9 json.CheckNotNull("json"); 10 return JsonConvert.DeserializeObject<T>(json); 11 }
EntityFramework 使用 linq 查询匿名结果的方式很好用,性能又好,爽呆了,但由于匿名结果的可访问性是 internal,可是不能直接返回给View(View在编译之后是另外一个程序集了,internal不能跨程序集)使用,来个扩展方法专门把匿名对象转换为dynamic吧
1 /// <summary> 2 /// 将对象[主要是匿名对象]转换为dynamic 3 /// </summary> 4 public static dynamic ToDynamic(this object value) 5 { 6 IDictionary<string, object> expando = new ExpandoObject(); 7 Type type = value.GetType(); 8 PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(type); 9 foreach (PropertyDescriptor property in properties) 10 { 11 var val = property.GetValue(value); 12 if (property.PropertyType.FullName.StartsWith("<>f__AnonymousType")) 13 { 14 dynamic dval = val.ToDynamic(); 15 expando.Add(property.Name, dval); 16 } 17 else 18 { 19 expando.Add(property.Name, val); 20 } 21 } 22 return expando as ExpandoObject; 23 }
使用示例:
1 // Controller 端,转换为dynamic 2 var data = new { Id = 1, Name = "GMF" }; 3 dynamic result = data.ToDynamic(); 4 Viewbag.Result = result; 5 ... 6 //View 端,可以直接用了 7 dynamic result = Viewbag.Result; 8 @result.Id 9 @result.Name 10 ...
使用正则表达式的原生静态方法,比较难用,做成扩展方法就好用多了
1 /// <summary> 2 /// 指示所指定的正则表达式在指定的输入字符串中是否找到了匹配项 3 /// </summary> 4 /// <param name="value">要搜索匹配项的字符串</param> 5 /// <param name="pattern">要匹配的正则表达式模式</param> 6 /// <returns>如果正则表达式找到匹配项,则为 true;否则,为 false</returns> 7 public static bool IsMatch(this string value, string pattern) 8 { 9 if (value == null) 10 { 11 return false; 12 } 13 return Regex.IsMatch(value, pattern); 14 } 15 16 /// <summary> 17 /// 在指定的输入字符串中搜索指定的正则表达式的第一个匹配项 18 /// </summary> 19 /// <param name="value">要搜索匹配项的字符串</param> 20 /// <param name="pattern">要匹配的正则表达式模式</param> 21 /// <returns>一个对象,包含有关匹配项的信息</returns> 22 public static string Match(this string value, string pattern) 23 { 24 if (value == null) 25 { 26 return null; 27 } 28 return Regex.Match(value, pattern).Value; 29 } 30 31 /// <summary> 32 /// 在指定的输入字符串中搜索指定的正则表达式的所有匹配项的字符串集合 33 /// </summary> 34 /// <param name="value"> 要搜索匹配项的字符串 </param> 35 /// <param name="pattern"> 要匹配的正则表达式模式 </param> 36 /// <returns> 一个集合,包含有关匹配项的字符串值 </returns> 37 public static IEnumerable<string> Matches(this string value, string pattern) 38 { 39 if (value == null) 40 { 41 return new string[] { }; 42 } 43 MatchCollection matches = Regex.Matches(value, pattern); 44 return from Match match in matches select match.Value; 45 }
1 /// <summary> 2 /// 是否电子邮件 3 /// </summary> 4 public static bool IsEmail(this string value) 5 { 6 const string pattern = @"^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$"; 7 return value.IsMatch(pattern); 8 } 9 10 /// <summary> 11 /// 是否是IP地址 12 /// </summary> 13 public static bool IsIpAddress(this string value) 14 { 15 const string pattern = @"^(\d(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\d\.){3}\d(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\d$"; 16 return value.IsMatch(pattern); 17 } 18 19 /// <summary> 20 /// 是否是整数 21 /// </summary> 22 public static bool IsNumeric(this string value) 23 { 24 const string pattern = @"^\-?[0-9]+$"; 25 return value.IsMatch(pattern); 26 } 27 28 /// <summary> 29 /// 是否是Unicode字符串 30 /// </summary> 31 public static bool IsUnicode(this string value) 32 { 33 const string pattern = @"^[\u4E00-\u9FA5\uE815-\uFA29]+$"; 34 return value.IsMatch(pattern); 35 } 36 37 /// <summary> 38 /// 是否Url字符串 39 /// </summary> 40 public static bool IsUrl(this string value) 41 { 42 const string pattern = @"^(http|https|ftp|rtsp|mms):(\/\/|\\\\)[A-Za-z0-9%\-_@]+\.[A-Za-z0-9%\-_@]+[A-Za-z0-9\.\/=\?%\-&_~`@:\+!;]*$"; 43 return value.IsMatch(pattern); 44 } 45 46 /// <summary> 47 /// 是否身份证号,验证如下3种情况: 48 /// 1.身份证号码为15位数字; 49 /// 2.身份证号码为18位数字; 50 /// 3.身份证号码为17位数字+1个字母 51 /// </summary> 52 public static bool IsIdentityCard(this string value) 53 { 54 const string pattern = @"^(^\d{15}$|^\d{18}$|^\d{17}(\d|X|x))$"; 55 return value.IsMatch(pattern); 56 } 57 58 /// <summary> 59 /// 是否手机号码 60 /// </summary> 61 /// <param name="value"></param> 62 /// <param name="isRestrict">是否按严格格式验证</param> 63 public static bool IsMobileNumber(this string value, bool isRestrict = false) 64 { 65 string pattern = isRestrict ? @"^[1][3-8]\d{9}$" : @"^[1]\d{10}$"; 66 return value.IsMatch(pattern); 67 }
字符串的静态方法有时候用着实在别扭,包装成扩展方法,在编码的时候就感觉顺畅多了。
1 /// <summary> 2 /// 指示指定的字符串是 null 还是 System.String.Empty 字符串 3 /// </summary> 4 public static bool IsNullOrEmpty(this string value) 5 { 6 return string.IsNullOrEmpty(value); 7 } 8 9 /// <summary> 10 /// 指示指定的字符串是 null、空还是仅由空白字符组成。 11 /// </summary> 12 public static bool IsNullOrWhiteSpace(this string value) 13 { 14 return string.IsNullOrWhiteSpace(value); 15 } 16 17 /// <summary> 18 /// 为指定格式的字符串填充相应对象来生成字符串 19 /// </summary> 20 /// <param name="format">字符串格式,占位符以{n}表示</param> 21 /// <param name="args">用于填充占位符的参数</param> 22 /// <returns>格式化后的字符串</returns> 23 public static string FormatWith(this string format, params object[] args) 24 { 25 format.CheckNotNull("format"); 26 return string.Format(CultureInfo.CurrentCulture, format, args); 27 } 28 29 /// <summary> 30 /// 将字符串反转 31 /// </summary> 32 /// <param name="value">要反转的字符串</param> 33 public static string ReverseString(this string value) 34 { 35 value.CheckNotNull("value"); 36 return new string(value.Reverse().ToArray()); 37 } 38 39 /// <summary> 40 /// 以指定字符串作为分隔符将指定字符串分隔成数组 41 /// </summary> 42 /// <param name="value">要分割的字符串</param> 43 /// <param name="strSplit">字符串类型的分隔符</param> 44 /// <param name="removeEmptyEntries">是否移除数据中元素为空字符串的项</param> 45 /// <returns>分割后的数据</returns> 46 public static string[] Split(this string value, string strSplit, bool removeEmptyEntries = false) 47 { 48 return value.Split(new[] { strSplit }, removeEmptyEntries ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None); 49 } 50 51 /// <summary> 52 /// 获取字符串的MD5 Hash值 53 /// </summary> 54 public static string ToMd5Hash(this string value) 55 { 56 return HashHelper.GetMd5(value); 57 }
类型相关的代码,通常都是一些反射的代码,利用扩展方法,可以使操作有效的简化。
1 /// <summary> 2 /// 判断类型是否为Nullable类型 3 /// </summary> 4 /// <param name="type"> 要处理的类型 </param> 5 /// <returns> 是返回True,不是返回False </returns> 6 public static bool IsNullableType(this Type type) 7 { 8 return ((type != null) && type.IsGenericType) && (type.GetGenericTypeDefinition() == typeof(Nullable<>)); 9 } 10 11 /// <summary> 12 /// 通过类型转换器获取Nullable类型的基础类型 13 /// </summary> 14 /// <param name="type"> 要处理的类型对象 </param> 15 /// <returns> </returns> 16 public static Type GetUnNullableType(this Type type) 17 { 18 if (IsNullableType(type)) 19 { 20 NullableConverter nullableConverter = new NullableConverter(type); 21 return nullableConverter.UnderlyingType; 22 } 23 return type; 24 }
使用示例:
1 Assert.IsTrue(typeof(int?).IsNullableType()); 2 Assert.IsTrue(typeof(Nullable<int>).IsNullableType()); 3 Assert.IsFalse(typeof(int).IsNullableType()); 4 5 Assert.AreEqual(typeof(int?).GetUnNullableType(), typeof(int)); 6 Assert.AreEqual(typeof(Nullable<int>).GetUnNullableType(), typeof(int)); 7 Assert.AreEqual(typeof(int).GetUnNullableType(), typeof(int));
1 /// <summary> 2 /// 获取成员元数据的Description特性描述信息 3 /// </summary> 4 /// <param name="member">成员元数据对象</param> 5 /// <param name="inherit">是否搜索成员的继承链以查找描述特性</param> 6 /// <returns>返回Description特性描述信息,如不存在则返回成员的名称</returns> 7 public static string ToDescription(this MemberInfo member, bool inherit = false) 8 { 9 DescriptionAttribute desc = member.GetAttribute<DescriptionAttribute>(inherit); 10 return desc == null ? member.Name : desc.Description; 11 } 12 13 /// <summary> 14 /// 检查指定指定类型成员中是否存在指定的Attribute特性 15 /// </summary> 16 /// <typeparam name="T">要检查的Attribute特性类型</typeparam> 17 /// <param name="memberInfo">要检查的类型成员</param> 18 /// <param name="inherit">是否从继承中查找</param> 19 /// <returns>是否存在</returns> 20 public static bool AttributeExists<T>(this MemberInfo memberInfo, bool inherit = false) where T : Attribute 21 { 22 return memberInfo.GetCustomAttributes(typeof(T), inherit).Any(m => (m as T) != null); 23 } 24 25 /// <summary> 26 /// 从类型成员获取指定Attribute特性 27 /// </summary> 28 /// <typeparam name="T">Attribute特性类型</typeparam> 29 /// <param name="memberInfo">类型类型成员</param> 30 /// <param name="inherit">是否从继承中查找</param> 31 /// <returns>存在返回第一个,不存在返回null</returns> 32 public static T GetAttribute<T>(this MemberInfo memberInfo, bool inherit = false) where T : Attribute 33 { 34 var descripts = memberInfo.GetCustomAttributes(typeof(T), inherit); 35 return descripts.FirstOrDefault() as T; 36 } 37 38 /// <summary> 39 /// 从类型成员获取指定Attribute特性 40 /// </summary> 41 /// <typeparam name="T">Attribute特性类型</typeparam> 42 /// <param name="memberInfo">类型类型成员</param> 43 /// <param name="inherit">是否从继承中查找</param> 44 /// <returns>返回所有指定Attribute特性的数组</returns> 45 public static T[] GetAttributes<T>(this MemberInfo memberInfo, bool inherit = false) where T : Attribute 46 { 47 return memberInfo.GetCustomAttributes(typeof(T), inherit).Cast<T>().ToArray(); 48 }
使用示例:
//测试类型 [Description("测试实体")] public class TestEntity { [Description("名称")] public string Name { get; set; } } //使用示例 Type type = typeof(TestEntity); Assert.AreEqual(type.ToDescription(), "测试实体"); PropertyInfo property = type.GetProperty("Name"); Assert.AreEqual(property.ToDescription(), "名称"); type = typeof(string); Assert.AreEqual(type.ToDescription(), "String"); //没有指定特性,返回类型名称
1 /// <summary> 2 /// 判断类型是否为集合类型 3 /// </summary> 4 /// <param name="type">要处理的类型</param> 5 /// <returns>是返回True,不是返回False</returns> 6 public static bool IsEnumerable(this Type type) 7 { 8 if (type == typeof(string)) 9 { 10 return false; 11 } 12 return typeof(IEnumerable).IsAssignableFrom(type); 13 } 14 15 /// <summary> 16 /// 判断当前泛型类型是否可由指定类型的实例填充 17 /// </summary> 18 /// <param name="genericType">泛型类型</param> 19 /// <param name="type">指定类型</param> 20 /// <returns></returns> 21 public static bool IsGenericAssignableFrom(this Type genericType, Type type) 22 { 23 genericType.CheckNotNull("genericType"); 24 type.CheckNotNull("type"); 25 if (!genericType.IsGenericType) 26 { 27 throw new ArgumentException("该功能只支持泛型类型的调用,非泛型类型可使用 IsAssignableFrom 方法。"); 28 } 29 30 List<Type> allOthers = new List<Type> { type }; 31 if (genericType.IsInterface) 32 { 33 allOthers.AddRange(type.GetInterfaces()); 34 } 35 36 foreach (var other in allOthers) 37 { 38 Type cur = other; 39 while (cur != null) 40 { 41 if (cur.IsGenericType) 42 { 43 cur = cur.GetGenericTypeDefinition(); 44 } 45 if (cur.IsSubclassOf(genericType) || cur == genericType) 46 { 47 return true; 48 } 49 cur = cur.BaseType; 50 } 51 } 52 return false; 53 }
Random 随机数功能,也是非常常用的,.net fx 中已经定义了比如Next,NextDouble这些功能了,下面我们再定义一些其他的常用随机数操作,在编写代码的时候,更行云流水。
注:Random 的随机性是伪随机的,最好使用全局存在的Random对象,才能保证随机性,如果是即时声明的Random对象,在短时间内可能失去随机性。
1 /// <summary> 2 /// 返回随机布尔值 3 /// </summary> 4 /// <param name="random"></param> 5 /// <returns>随机布尔值</returns> 6 public static bool NextBoolean(this Random random) 7 { 8 return random.NextDouble() > 0.5; 9 } 10 11 /// <summary> 12 /// 返回指定枚举类型的随机枚举值 13 /// </summary> 14 /// <param name="random"></param> 15 /// <returns>指定枚举类型的随机枚举值</returns> 16 public static T NextEnum<T>(this Random random) where T : struct 17 { 18 Type type = typeof(T); 19 if (!type.IsEnum) 20 { 21 throw new InvalidOperationException(); 22 } 23 Array array = Enum.GetValues(type); 24 int index = random.Next(array.GetLowerBound(0), array.GetUpperBound(0) + 1); 25 return (T)array.GetValue(index); 26 } 27 28 /// <summary> 29 /// 返回随机数填充的指定长度的数组 30 /// </summary> 31 /// <param name="random"></param> 32 /// <param name="length">数组长度</param> 33 /// <returns>随机数填充的指定长度的数组</returns> 34 public static byte[] NextBytes(this Random random, int length) 35 { 36 if (length < 0) 37 { 38 throw new ArgumentOutOfRangeException("length"); 39 } 40 byte[] data = new byte[length]; 41 random.NextBytes(data); 42 return data; 43 } 44 45 /// <summary> 46 /// 返回数组中的随机元素 47 /// </summary> 48 /// <typeparam name="T">元素类型</typeparam> 49 /// <param name="random"></param> 50 /// <param name="items">元素数组</param> 51 /// <returns>元素数组中的某个随机项</returns> 52 public static T NextItem<T>(this Random random, T[] items) 53 { 54 return items[random.Next(0, items.Length)]; 55 } 56 57 /// <summary> 58 /// 返回指定时间段内的随机时间值 59 /// </summary> 60 /// <param name="random"></param> 61 /// <param name="minValue">时间范围的最小值</param> 62 /// <param name="maxValue">时间范围的最大值</param> 63 /// <returns>指定时间段内的随机时间值</returns> 64 public static DateTime NextDateTime(this Random random, DateTime minValue, DateTime maxValue) 65 { 66 long ticks = minValue.Ticks + (long)((maxValue.Ticks - minValue.Ticks) * random.NextDouble()); 67 return new DateTime(ticks); 68 } 69 70 /// <summary> 71 /// 返回随机时间值 72 /// </summary> 73 /// <param name="random"></param> 74 /// <returns>随机时间值</returns> 75 public static DateTime NextDateTime(this Random random) 76 { 77 return NextDateTime(random, DateTime.MinValue, DateTime.MaxValue); 78 } 79 80 /// <summary> 81 /// 获取指定的长度的随机数字字符串 82 /// </summary> 83 /// <param name="random"></param> 84 /// <param name="length">要获取随机数长度</param> 85 /// <returns>指定长度的随机数字符串</returns> 86 public static string GetRandomNumberString(this Random random, int length) 87 { 88 if (length < 0) 89 { 90 throw new ArgumentOutOfRangeException("length"); 91 } 92 char[] pattern = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; 93 string result = ""; 94 int n = pattern.Length; 95 for (int i = 0; i < length; i++) 96 { 97 int rnd = random.Next(0, n); 98 result += pattern[rnd]; 99 } 100 return result; 101 } 102 103 /// <summary> 104 /// 获取指定的长度的随机字母字符串 105 /// </summary> 106 /// <param name="random"></param> 107 /// <param name="length">要获取随机数长度</param> 108 /// <returns>指定长度的随机字母组成字符串</returns> 109 public static string GetRandomLetterString(this Random random, int length) 110 { 111 if (length < 0) 112 { 113 throw new ArgumentOutOfRangeException("length"); 114 } 115 char[] pattern = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 116 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; 117 string result = ""; 118 int n = pattern.Length; 119 for (int i = 0; i < length; i++) 120 { 121 int rnd = random.Next(0, n); 122 result += pattern[rnd]; 123 } 124 return result; 125 } 126 127 /// <summary> 128 /// 获取指定的长度的随机字母和数字字符串 129 /// </summary> 130 /// <param name="random"></param> 131 /// <param name="length">要获取随机数长度</param> 132 /// <returns>指定长度的随机字母和数字组成字符串</returns> 133 public static string GetRandomLetterAndNumberString(this Random random, int length) 134 { 135 if (length < 0) 136 { 137 throw new ArgumentOutOfRangeException("length"); 138 } 139 char[] pattern = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 140 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 141 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; 142 string result = ""; 143 int n = pattern.Length; 144 for (int i = 0; i < length; i++) 145 { 146 int rnd = random.Next(0, n); 147 result += pattern[rnd]; 148 } 149 return result; 150 }
一个饱满健壮的软件,检查用户输入是必不少的步骤,永远也不要相信用户输入的数据,即早对输入的非法数据进行拦截,有利于及早发现错误,使非法数据的破坏减至最小。
参数检查至关重要,那么,什么时候该进行参数检查呢,我个人认为:
通常我们做参数检查,会使用如下的方式
1 public static void Method(string input, int number) 2 { 3 if (input == null) 4 { 5 throw new ArgumentNullException("input"); 6 } 7 if (number <= 0) 8 { 9 throw new ArgumentOutOfRangeException("number"); 10 } 11 // do something 12 }
写法虽然不复杂,但整天写这样的代码,也会累的,而且一个简单就得占4行的空间,我们需要一种更简单的方式。为了适应各种异常的抛出,我们定义一个异常的泛型方法来执行最终的异常抛出。
1 /// <summary> 2 /// 验证指定值的断言<paramref name="assertion"/>是否为真,如果不为真,抛出指定消息<paramref name="message"/>的指定类型<typeparamref name="TException"/>异常 3 /// </summary> 4 /// <typeparam name="TException">异常类型</typeparam> 5 /// <param name="assertion">要验证的断言。</param> 6 /// <param name="message">异常消息。</param> 7 private static void Require<TException>(bool assertion, string message) where TException : Exception 8 { 9 if (assertion) 10 { 11 return; 12 } 13 TException exception = (TException)Activator.CreateInstance(typeof(TException), message); 14 throw exception; 15 }
定义了一个bool参数assertion来接收是是否要抛出异常的判断结果,同时还需要抛出异常消息。看到上面这段代码,有人就坐不住了,这里使用的反射来实例化异常实例,会带来性能问题吗?大可不必,试想,都异常了,还需要在乎这个的性能问题吗?
前面的ArgumentNullException异常,可以定义如下的扩展方法
1 /// <summary> 2 /// 检查参数不能为空引用,否则抛出<see cref="ArgumentNullException"/>异常。 3 /// </summary> 4 /// <param name="value"></param> 5 /// <param name="paramName">参数名称</param> 6 /// <exception cref="ArgumentNullException"></exception> 7 public static void CheckNotNull<T>(this T value, string paramName) where T : class 8 { 9 Require<ArgumentNullException>(value != null, string.Format(Resources.ParameterCheck_NotNull, paramName)); 10 }
这样,要对 input 参数进行检查,就相当的简单了: input.CheckNotNull("input");
类似的,对于引用类型,我们可以定义出如下扩展方法:
1 /// <summary> 2 /// 检查字符串不能为空引用或空字符串,否则抛出<see cref="ArgumentNullException"/>异常或<see cref="ArgumentException"/>异常。 3 /// </summary> 4 /// <param name="value"></param> 5 /// <param name="paramName">参数名称。</param> 6 /// <exception cref="ArgumentNullException"></exception> 7 /// <exception cref="ArgumentException"></exception> 8 public static void CheckNotNullOrEmpty(this string value, string paramName) 9 { 10 value.CheckNotNull(paramName); 11 Require<ArgumentException>(value.Length > 0, string.Format(Resources.ParameterCheck_NotNullOrEmpty_String, paramName)); 12 } 13 14 /// <summary> 15 /// 检查Guid值不能为Guid.Empty,否则抛出<see cref="ArgumentException"/>异常。 16 /// </summary> 17 /// <param name="value"></param> 18 /// <param name="paramName">参数名称。</param> 19 /// <exception cref="ArgumentException"></exception> 20 public static void CheckNotEmpty(this Guid value, string paramName) 21 { 22 Require<ArgumentException>(value != Guid.Empty, string.Format(Resources.ParameterCheck_NotEmpty_Guid, paramName)); 23 } 24 25 /// <summary> 26 /// 检查集合不能为空引用或空集合,否则抛出<see cref="ArgumentNullException"/>异常或<see cref="ArgumentException"/>异常。 27 /// </summary> 28 /// <typeparam name="T">集合项的类型。</typeparam> 29 /// <param name="collection"></param> 30 /// <param name="paramName">参数名称。</param> 31 /// <exception cref="ArgumentNullException"></exception> 32 /// <exception cref="ArgumentException"></exception> 33 public static void CheckNotNullOrEmpty<T>(this IEnumerable<T> collection, string paramName) 34 { 35 collection.CheckNotNull(paramName); 36 Require<ArgumentException>(collection.Any(), string.Format(Resources.ParameterCheck_NotNullOrEmpty_Collection, paramName)); 37 }
对于值类型,可以定义出如下扩展方法:
1 /// <summary> 2 /// 检查参数必须小于[或可等于,参数canEqual]指定值,否则抛出<see cref="ArgumentOutOfRangeException"/>异常。 3 /// </summary> 4 /// <typeparam name="T">参数类型。</typeparam> 5 /// <param name="value"></param> 6 /// <param name="paramName">参数名称。</param> 7 /// <param name="target">要比较的值。</param> 8 /// <param name="canEqual">是否可等于。</param> 9 /// <exception cref="ArgumentOutOfRangeException"></exception> 10 public static void CheckLessThan<T>(this T value, string paramName, T target, bool canEqual = false) where T : IComparable<T> 11 { 12 bool flag = canEqual ? value.CompareTo(target) <= 0 : value.CompareTo(target) < 0; 13 string format = canEqual ? Resources.ParameterCheck_NotLessThanOrEqual : Resources.ParameterCheck_NotLessThan; 14 Require<ArgumentOutOfRangeException>(flag, string.Format(format, paramName, target)); 15 } 16 17 /// <summary> 18 /// 检查参数必须大于[或可等于,参数canEqual]指定值,否则抛出<see cref="ArgumentOutOfRangeException"/>异常。 19 /// </summary> 20 /// <typeparam name="T">参数类型。</typeparam> 21 /// <param name="value"></param> 22 /// <param name="paramName">参数名称。</param> 23 /// <param name="target">要比较的值。</param> 24 /// <param name="canEqual">是否可等于。</param> 25 /// <exception cref="ArgumentOutOfRangeException"></exception> 26 public static void CheckGreaterThan<T>(this T value, string paramName, T target, bool canEqual = false) where T : IComparable<T> 27 { 28 bool flag = canEqual ? value.CompareTo(target) >= 0 : value.CompareTo(target) > 0; 29 string format = canEqual ? Resources.ParameterCheck_NotGreaterThanOrEqual : Resources.ParameterCheck_NotGreaterThan; 30 Require<ArgumentOutOfRangeException>(flag, string.Format(format, paramName, target)); 31 } 32 33 /// <summary> 34 /// 检查参数必须在指定范围之间,否则抛出<see cref="ArgumentOutOfRangeException"/>异常。 35 /// </summary> 36 /// <typeparam name="T">参数类型。</typeparam> 37 /// <param name="value"></param> 38 /// <param name="paramName">参数名称。</param> 39 /// <param name="start">比较范围的起始值。</param> 40 /// <param name="end">比较范围的结束值。</param> 41 /// <param name="startEqual">是否可等于起始值</param> 42 /// <param name="endEqual">是否可等于结束值</param> 43 /// <exception cref="ArgumentOutOfRangeException"></exception> 44 public static void CheckBetween<T>(this T value, string paramName, T start, T end, bool startEqual = false, bool endEqual = false) 45 where T : IComparable<T> 46 { 47 bool flag = startEqual ? value.CompareTo(start) >= 0 : value.CompareTo(start) > 0; 48 string message = startEqual 49 ? string.Format(Resources.ParameterCheck_BetweenNotEqual, paramName, start, end, start) 50 : string.Format(Resources.ParameterCheck_Between, paramName, start, end); 51 Require<ArgumentOutOfRangeException>(flag, message); 52 53 flag = endEqual ? value.CompareTo(end) <= 0 : value.CompareTo(end) < 0; 54 message = endEqual 55 ? string.Format(Resources.ParameterCheck_BetweenNotEqual, paramName, start, end, end) 56 : string.Format(Resources.ParameterCheck_Between, paramName, start, end); 57 Require<ArgumentOutOfRangeException>(flag, message); 58 }
常用的参数检查还有文件夹与文件是否存在的检查:
1 /// <summary> 2 /// 检查指定路径的文件夹必须存在,否则抛出<see cref="DirectoryNotFoundException"/>异常。 3 /// </summary> 4 /// <param name="directory"></param> 5 /// <param name="paramName">参数名称。</param> 6 /// <exception cref="ArgumentNullException"></exception> 7 /// <exception cref="DirectoryNotFoundException"></exception> 8 public static void CheckDirectoryExists(this string directory, string paramName = null) 9 { 10 CheckNotNull(directory, paramName); 11 Require<DirectoryNotFoundException>(Directory.Exists(directory), string.Format(Resources.ParameterCheck_DirectoryNotExists, directory)); 12 } 13 14 /// <summary> 15 /// 检查指定路径的文件必须存在,否则抛出<see cref="FileNotFoundException"/>异常。 16 /// </summary> 17 /// <param name="filename"></param> 18 /// <param name="paramName">参数名称。</param> 19 /// <exception cref="ArgumentNullException">当文件路径为null时</exception> 20 /// <exception cref="FileNotFoundException">当文件路径不存在时</exception> 21 public static void CheckFileExists(this string filename, string paramName = null) 22 { 23 CheckNotNull(filename, paramName); 24 Require<FileNotFoundException>(File.Exists(filename), string.Format(Resources.ParameterCheck_FileNotExists, filename)); 25 }
当然,还可能会有其他的情况,抛出其他类型的异常,定义如下两个通用的扩展:
1 /// <summary> 2 /// 验证指定值的断言表达式是否为真,不为值抛出<see cref="Exception"/>异常 3 /// </summary> 4 /// <param name="value"></param> 5 /// <param name="assertionFunc">要验证的断言表达式</param> 6 /// <param name="message">异常消息</param> 7 public static void Required<T>(this T value, Func<T, bool> assertionFunc, string message) 8 { 9 Require<Exception>(assertionFunc(value), message); 10 } 11 12 /// <summary> 13 /// 验证指定值的断言表达式是否为真,不为真抛出<typeparam name="TException>"/>异常 14 /// </summary> 15 /// <typeparam name="T">要判断的值的类型</typeparam> 16 /// <typeparam name="TException">抛出的异常类型</typeparam> 17 /// <param name="value">要判断的值</param> 18 /// <param name="assertionFunc">要验证的断言表达式</param> 19 /// <param name="message">异常消息</param> 20 public static void Required<T, TException>(this T value, Func<T, bool> assertionFunc, string message) where TException : Exception 21 { 22 Require<TException>(assertionFunc(value), message); 23 }
在使用Expression进行linq查询时,有时候需要对表达式进行 And,Or 等组合,可定义如下扩展方法来简化操作
1 /// <summary> 2 /// 以特定的条件运行组合两个Expression表达式 3 /// </summary> 4 /// <typeparam name="T">表达式的主实体类型</typeparam> 5 /// <param name="first">第一个Expression表达式</param> 6 /// <param name="second">要组合的Expression表达式</param> 7 /// <param name="merge">组合条件运算方式</param> 8 /// <returns>组合后的表达式</returns> 9 public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge) 10 { 11 Dictionary<ParameterExpression, ParameterExpression> map = 12 first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f); 13 Expression secondBody = ParameterRebinder.ReplaceParameters(map, second.Body); 14 return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters); 15 } 16 17 /// <summary> 18 /// 以 Expression.AndAlso 组合两个Expression表达式 19 /// </summary> 20 /// <typeparam name="T">表达式的主实体类型</typeparam> 21 /// <param name="first">第一个Expression表达式</param> 22 /// <param name="second">要组合的Expression表达式</param> 23 /// <returns>组合后的表达式</returns> 24 public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) 25 { 26 return first.Compose(second, Expression.AndAlso); 27 } 28 29 /// <summary> 30 /// 以 Expression.OrElse 组合两个Expression表达式 31 /// </summary> 32 /// <typeparam name="T">表达式的主实体类型</typeparam> 33 /// <param name="first">第一个Expression表达式</param> 34 /// <param name="second">要组合的Expression表达式</param> 35 /// <returns>组合后的表达式</returns> 36 public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) 37 { 38 return first.Compose(second, Expression.OrElse); 39 } 40 41 42 private class ParameterRebinder : ExpressionVisitor 43 { 44 private readonly Dictionary<ParameterExpression, ParameterExpression> _map; 45 46 private ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map) 47 { 48 _map = map ?? new Dictionary<ParameterExpression, ParameterExpression>(); 49 } 50 51 public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp) 52 { 53 return new ParameterRebinder(map).Visit(exp); 54 } 55 56 protected override Expression VisitParameter(ParameterExpression node) 57 { 58 ParameterExpression replacement; 59 if (_map.TryGetValue(node, out replacement)) 60 { 61 node = replacement; 62 } 63 return base.VisitParameter(node); 64 } 65 }
使用示例:
1 // And 2 Expression<Func<TestEntity, bool>> predicate = m => m.IsDeleted; 3 Expression<Func<TestEntity, bool>> actual = m => m.IsDeleted && m.Id > 500; 4 predicate = predicate.And(m => m.Id > 500); 5 List<TestEntity> list1 = Entities.Where(predicate.Compile()).ToList(); 6 List<TestEntity> list2 = Entities.Where(actual.Compile()).ToList(); 7 Assert.IsTrue(list1.SequenceEqual(list2)); 8 9 //Or 10 Expression<Func<TestEntity, bool>> predicate = m => m.IsDeleted; 11 Expression<Func<TestEntity, bool>> actual = m => m.IsDeleted || m.Id > 500; 12 predicate = predicate.Or(m => m.Id > 500); 13 List<TestEntity> list1 = Entities.Where(predicate.Compile()).ToList(); 14 List<TestEntity> list2 = Entities.Where(actual.Compile()).ToList(); 15 Assert.IsTrue(list1.SequenceEqual(list2));
集合操作扩展,主要是对IEnumerable<T>与IQueryable<T>两个集合进行扩展
1 /// <summary> 2 /// 集合扩展方法类 3 /// </summary> 4 public static class CollectionExtensions 5 { 6 #region IEnumerable的扩展 7 8 /// <summary> 9 /// 将集合展开并分别转换成字符串,再以指定的分隔符衔接,拼成一个字符串返回。默认分隔符为逗号 10 /// </summary> 11 /// <param name="collection"> 要处理的集合 </param> 12 /// <param name="separator"> 分隔符,默认为逗号 </param> 13 /// <returns> 拼接后的字符串 </returns> 14 public static string ExpandAndToString<T>(this IEnumerable<T> collection, string separator = ",") 15 { 16 return collection.ExpandAndToString(t => t.ToString(), separator); 17 } 18 19 /// <summary> 20 /// 循环集合的每一项,调用委托生成字符串,返回合并后的字符串。默认分隔符为逗号 21 /// </summary> 22 /// <param name="collection">待处理的集合</param> 23 /// <param name="itemFormatFunc">单个集合项的转换委托</param> 24 /// <param name="separetor">分隔符,默认为逗号</param> 25 /// <typeparam name="T">泛型类型</typeparam> 26 /// <returns></returns> 27 public static string ExpandAndToString<T>(this IEnumerable<T> collection, Func<T, string> itemFormatFunc, string separetor = ",") 28 { 29 collection = collection as IList<T> ?? collection.ToList(); 30 itemFormatFunc.CheckNotNull("itemFormatFunc"); 31 if (!collection.Any()) 32 { 33 return null; 34 } 35 StringBuilder sb = new StringBuilder(); 36 int i = 0; 37 int count = collection.Count(); 38 foreach (T t in collection) 39 { 40 if (i == count - 1) 41 { 42 sb.Append(itemFormatFunc(t)); 43 } 44 else 45 { 46 sb.Append(itemFormatFunc(t) + separetor); 47 } 48 i++; 49 } 50 return sb.ToString(); 51 } 52 53 /// <summary> 54 /// 集合是否为空 55 /// </summary> 56 /// <param name="collection"> 要处理的集合 </param> 57 /// <typeparam name="T"> 动态类型 </typeparam> 58 /// <returns> 为空返回True,不为空返回False </returns> 59 public static bool IsEmpty<T>(this IEnumerable<T> collection) 60 { 61 collection = collection as IList<T> ?? collection.ToList(); 62 return !collection.Any(); 63 } 64 65 /// <summary> 66 /// 根据第三方条件是否为真来决定是否执行指定条件的查询 67 /// </summary> 68 /// <param name="source"> 要查询的源 </param> 69 /// <param name="predicate"> 查询条件 </param> 70 /// <param name="condition"> 第三方条件 </param> 71 /// <typeparam name="T"> 动态类型 </typeparam> 72 /// <returns> 查询的结果 </returns> 73 public static IEnumerable<T> WhereIf<T>(this IEnumerable<T> source, Func<T, bool> predicate, bool condition) 74 { 75 predicate.CheckNotNull("predicate"); 76 source = source as IList<T> ?? source.ToList(); 77 78 return condition ? source.Where(predicate) : source; 79 } 80 81 /// <summary> 82 /// 根据指定条件返回集合中不重复的元素 83 /// </summary> 84 /// <typeparam name="T">动态类型</typeparam> 85 /// <typeparam name="TKey">动态筛选条件类型</typeparam> 86 /// <param name="source">要操作的源</param> 87 /// <param name="keySelector">重复数据筛选条件</param> 88 /// <returns>不重复元素的集合</returns> 89 public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector) 90 { 91 keySelector.CheckNotNull("keySelector"); 92 source = source as IList<T> ?? source.ToList(); 93 94 return source.GroupBy(keySelector).Select(group => group.First()); 95 } 96 97 #endregion 98 99 #region IQueryable的扩展 100 101 /// <summary> 102 /// 根据第三方条件是否为真来决定是否执行指定条件的查询 103 /// </summary> 104 /// <param name="source"> 要查询的源 </param> 105 /// <param name="predicate"> 查询条件 </param> 106 /// <param name="condition"> 第三方条件 </param> 107 /// <typeparam name="T"> 动态类型 </typeparam> 108 /// <returns> 查询的结果 </returns> 109 public static IQueryable<T> WhereIf<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate, bool condition) 110 { 111 source.CheckNotNull("source"); 112 predicate.CheckNotNull("predicate"); 113 114 return condition ? source.Where(predicate) : source; 115 } 116 117 /// <summary> 118 /// 把<see cref="IQueryable{T}"/>集合按指定字段与排序方式进行排序 119 /// </summary> 120 /// <param name="source">要排序的数据集</param> 121 /// <param name="propertyName">排序属性名</param> 122 /// <param name="sortDirection">排序方向</param> 123 /// <typeparam name="T">动态类型</typeparam> 124 /// <returns>排序后的数据集</returns> 125 public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, 126 string propertyName, 127 ListSortDirection sortDirection = ListSortDirection.Ascending) 128 { 129 source.CheckNotNull("source"); 130 propertyName.CheckNotNullOrEmpty("propertyName"); 131 132 return QueryablePropertySorter<T>.OrderBy(source, propertyName, sortDirection); 133 } 134 135 /// <summary> 136 /// 把<see cref="IQueryable{T}"/>集合按指定字段排序条件进行排序 137 /// </summary> 138 /// <typeparam name="T">动态类型</typeparam> 139 /// <param name="source">要排序的数据集</param> 140 /// <param name="sortCondition">列表字段排序条件</param> 141 /// <returns></returns> 142 public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, SortCondition sortCondition) 143 { 144 source.CheckNotNull("source"); 145 sortCondition.CheckNotNull("sortCondition"); 146 147 return source.OrderBy(sortCondition.SortField, sortCondition.ListSortDirection); 148 } 149 150 /// <summary> 151 /// 把<see cref="IOrderedQueryable{T}"/>集合继续按指定字段排序方式进行排序 152 /// </summary> 153 /// <typeparam name="T">动态类型</typeparam> 154 /// <param name="source">要排序的数据集</param> 155 /// <param name="propertyName">排序属性名</param> 156 /// <param name="sortDirection">排序方向</param> 157 /// <returns></returns> 158 public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, 159 string propertyName, 160 ListSortDirection sortDirection = ListSortDirection.Ascending) 161 { 162 source.CheckNotNull("source"); 163 propertyName.CheckNotNullOrEmpty("propertyName"); 164 165 return QueryablePropertySorter<T>.ThenBy(source, propertyName, sortDirection); 166 } 167 168 /// <summary> 169 /// 把<see cref="IOrderedQueryable{T}"/>集合继续指定字段排序方式进行排序 170 /// </summary> 171 /// <typeparam name="T">动态类型</typeparam> 172 /// <param name="source">要排序的数据集</param> 173 /// <param name="sortCondition">列表字段排序条件</param> 174 /// <returns></returns> 175 public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, SortCondition sortCondition) 176 { 177 source.CheckNotNull("source"); 178 sortCondition.CheckNotNull("sortCondition"); 179 180 return source.ThenBy(sortCondition.SortField, sortCondition.ListSortDirection); 181 } 182 183 #endregion 184 }
使用示例:
1 //ExpandAndToString 2 List<int> source = new List<int>(); 3 //当为空集合时,返回null 4 Assert.AreEqual(source.ExpandAndToString(), null); 5 source.AddRange(new List<int>() { 1, 2, 3, 4, 5, 6 }); 6 //没有分隔符时,默认为逗号 7 Assert.AreEqual(source.ExpandAndToString(), "1,2,3,4,5,6"); 8 Assert.AreEqual(source.ExpandAndToString(null), "123456"); 9 Assert.AreEqual(source.ExpandAndToString(""), "123456"); 10 Assert.AreEqual(source.ExpandAndToString("|"), "1|2|3|4|5|6"); 11 12 List<int> source = new List<int> { 1, 2, 3, 4, 5, 6 }; 13 14 //转换委托不能为空 15 ExceptionAssert.IsException<ArgumentNullException>(() => source.ExpandAndToString(null)); 16 //没有分隔符时,默认为逗号 17 Assert.AreEqual(source.ExpandAndToString(item => (item + 1).ToString()), "2,3,4,5,6,7"); 18 Assert.AreEqual(source.ExpandAndToString(item => (item + 1).ToString(), "|"), "2|3|4|5|6|7"); 19 20 List<int> source = new List<int>(); 21 Assert.IsTrue(source.IsEmpty()); 22 23 source.Add(1); 24 Assert.IsFalse(source.IsEmpty()); 25 26 27 List<int> source = new List<int> { 1, 2, 3, 4, 5, 6, 7 }; 28 CollectionAssert.AreEqual(source.WhereIf(m => m > 5, false).ToList(), source); 29 List<int> actual = new List<int> { 6, 7 }; 30 CollectionAssert.AreEqual(source.WhereIf(m => m > 5, true).ToList(), actual); 31 32 33 List<int> source = new List<int> { 1, 2, 3, 3, 4, 4, 5, 6, 7, 7 }; 34 List<int> actual = new List<int> { 1, 2, 3, 4, 5, 6, 7 }; 35 CollectionAssert.AreEqual(source.DistinctBy(m => m).ToList(), actual);
OSharp项目已在github.com上开源,地址为:https://github.com/i66soft/osharp,欢迎阅读代码,欢迎 Fork,如果您认同 OSharp 项目的思想,欢迎参与 OSharp 项目的开发。
在Visual Studio 2013中,可直接获取 OSharp 的最新源代码,获取方式如下,地址为:https://github.com/i66soft/osharp.git
OSharp的相关类库已经发布到nuget上,欢迎试用,直接在nuget上搜索 “osharp” 关键字即可找到
本文已同步到系列目录:OSharp快速开发框架解说系列