【开源】OSharp框架解说系列(3):扩展方法

〇、前言

扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。 对于用 C# 和 Visual Basic 编写的客户端代码,调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异。

最常见的扩展方法是 LINQ 标准查询运算符,它将查询功能添加到现有的 System.Collections.IEnumerable 和 System.Collections.Generic.IEnumerable<T> 类型。 若要使用标准查询运算符,请先使用 using System.Linq 指令将它们置于范围中。 然后,任何实现了IEnumerable<T> 的类型看起来都具有 GroupByOrderByAverage 等实例方法。 在 IEnumerable<T> 类型的实例(如 List<T> 或 Array)后键入“dot”时,可以在 IntelliSense 语句完成中看到这些附加方法。

  以上摘自MSDN的扩展方法描述,可见扩展方法是个很实用的语法糖,用过都说好。

  OSharp 框架中也定义(收集)了不少非常实用的扩展方法,位于工具组件OSharp.Utility.dll中,命名空间为 OSharp.Utility.Extensions,下面我们来一一分解。

一、目录

  • 〇、前言
  • 一、目录
  • 二、泛型扩展
  • 三、字符串扩展
  • 四、类型扩展
  • 五、随机数扩展
  • 六、参数检查扩展
  • 七、Expression表达式扩展
  • 八、集合类扩展
  • 九、开源说明
  • 系列导航

二、泛型扩展

  泛型扩展,如果没有类型限定,基本上就是 Object 类型的扩展。因为所有类型都继承自Object类型的,如果给Objext类型定义扩展方法,那么所有的类型都会增加这个方法,有时候反而会阻碍编码流畅性。

  注:本篇都是非常基础的代码,代码的功能都有注释,可讲的东西不多,思想性的东西就更少了,不想看代码的可以跳过本篇。

  声明:本文的扩展方法部分出自原创,部分收集整理于互联网(出处已不可考),冒犯之处望见谅。

1.数据类型转换

  在编码的过程中,经常会遇到数据类型转换的需求,为了方便统一,我们可以定义一个通用的类型转换扩展方法。

 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>());

2.JSON序列化

  很多时候都要把对象序列化成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 }

3.MVC中的View直接使用 linq to entities 查询出来的匿名对象

  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%\[email protected]]+\.[A-Za-z0-9%\[email protected]]+[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 }

四、类型扩展

  类型相关的代码,通常都是一些反射的代码,利用扩展方法,可以使操作有效的简化。

Nullable类型

 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));

Attribute特性操作

 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表达式扩展

  在使用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);

九、开源说明

 (一)github.com

  OSharp项目已在github.com上开源,地址为:https://github.com/i66soft/osharp,欢迎阅读代码,欢迎 Fork,如果您认同 OSharp 项目的思想,欢迎参与 OSharp 项目的开发。

  在Visual Studio 2013中,可直接获取 OSharp 的最新源代码,获取方式如下,地址为:https://github.com/i66soft/osharp.git

  

 (二)nuget

  OSharp的相关类库已经发布到nuget上,欢迎试用,直接在nuget上搜索 “osharp” 关键字即可找到
  

系列导航

  1. 【开源】OSharp框架解说系列(1):总体设计
  2. 【开源】OSharp框架解说系列(2):从后台UI说起-EasyUI的后台界面搭建
  3. 【开源】OSharp框架解说系列(2.2):EasyUI复杂布局及数据操作
  4. 【开源】OSharp框架解说系列(3):扩展方法
时间: 2024-10-12 15:56:15

【开源】OSharp框架解说系列(3):扩展方法的相关文章

【开源】OSharp框架解说系列(4):架构分层及IoC

〇.前言 前面构造了一个后台管理的界面布局,下面开始讲解整个项目的分层设计. 关于分层,网上已经存在相当多的讨论了,这也是一个程序员初学架构设计最先会碰到的问题. 该不该分层? 怎样分层? 层与层之间是否需要解耦?是否需要设计接口?接口是否是多余的? 看完OSharp的分层设计,我想,你应该多少能得到一些启示. 注:OSharp 开发框架的前身是<MVC实体架构设计>系列中讲到的那个架构示例,所以有很多知识点那个系列讲到了,就不会在这个系列再重复了,如果有什么觉得不太明白的可以参考<MV

【开源】OSharp框架解说系列(6.1):日志系统设计

〇.前言 日志记录对于一个系统而言,重要性不言而喻.日志记录功能在系统开发阶段,往往容易被忽略.因为开发阶段,程序可以调试,可以反复的运行以查找问题.但在系统进入正常的运行维护阶段,特别是在进行审计统计的时候,追踪问题的时候,在追溯责任的时候,在系统出错的时候等等场景中,日志记录才会显示出它不可替代的作用.记录的日志,平时看似累赘,只有在需要的时候,才会拍大腿后悔当初为什么不把日志记录得详细些. 日志系统,是一个非常基础的系统,但由于需求的复杂性,各个场景需要的日志分类,来源,输出方式各有不同,

【开源】OSharp框架解说系列(5.2):EntityFramework数据层实现

〇.前言 上篇 的数据层设计中,我们主要设计了数据对对外开放的 实体基类EntityBase<TKey>,单元操作接口IUnitOfWork 和 数据仓储接口IRepository<TEntity, TKey>,下面我们来解说怎样来使用 EntityFramework 对这些数据访问需求进行实现.EntityFramework 的实现中,我们不仅要实现以上设计的两个接口,还要做以下几件事: 设计一个与 业务实体解耦的 EntityFramework数据上下文类 设计 实体加载方案,

【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构

〇.前言 要了解一个东西长什么样,至少得让我们能看到,才能提出针对性的见解.所以,为了言之有物,而不是凭空漫谈,我们先从UI说起,后台管理页面的UI我们将使用应用比较普遍的easyui框架. 以前在用easyui的时候,每个页面都得从0做起,或者不厌其烦地由以前的页面通过“复制-粘贴”的方式来修改,久页久之,就会造成页面庞大且难以维护.其实,前端的html,javascript代码与后端的代码是一样的,通过一定的组织,把重复的代码抽离出来,同样也通过达到很好的复用率.而MVC的天生的Layout

【开源】OSharp框架解说系列(2.2):EasyUI复杂布局及数据操作

一.目录 一.目录 二.EasyUI复杂布局 三.EasyUI动态工具栏 四.EasyUI增删改操作 五.开源说明 系列导航 二.EasyUI复杂布局 接上篇,前面我们已经定义了一个 datagrid父视图 _DataGridLayout.cshtml,实现一个表格是相当的容易.但是,实际业务中,并非所有的数据列表并非只是单一的datagrid列表,还可能需要把datagrid与其他组件配合使用,比如角色信息是来源于各个组织机构的,就需要增加一个组织机构的分类,来更好的管理各种角色.最终效果图如

【开源】OSharp框架解说系列(1):总体设计

〇.前言 哈,距离前一个系列<MVC实用构架设计>的烂尾篇(2013年9月1日)已经跨了两个年头了,今天是2015年1月9日,日期已经相映,让我们开启新的航程吧. 前一个系列讲的主要是我对架构设计的理解以及怎样用好EntityFramework的一些想法,在技术细节上并没有太多的考究.不幸的是,不少同学把这个架构当作框架来用了,里边留的很多坑,坑苦了很多人,真是误人子弟,深表愧疚.于是重新整理代码,整理思路,鼓捣出了这个我们将要详解的开源框架:OSharp.这次,我们真的深入地说框架了,而不是

OSharp3.0框架解说系列:新版本说明及新功能规划预览

前言 时间过得真快,小半年又过去了. OSharp在github.com开源已经半年了,半年时间里,我们发现开源并没有给OSharp带来什么发展,关注的人不多,提交Bug的人更少,至于愿意参与到项目中来,给OSharp提交代码的人,0. 大环境如此,我也没什么可说的. 一个人的开源,开的不是源,是寂寞. 为了OSharp项目能继续发展下去,也为了团队的积极性(大家都懂的,如果只有你一个人在贡献,别人都只索取,你的热情坚持不了多久的),我们做了一个决定…… OSharp3.0不再开源 从OShar

OSharp3.0框架解说系列(6.2):操作日志与数据日志

前言 在<[开源]OSharp框架解说系列(6.1):日志系统设计>中,我们已经设计并实现了一个可扩展的日志系统,只要定义好输出端的Adapter,就可以以任意形式输出日志信息. 在日志开发中,有些日志记录需求是常规需要的,比如操作日志,数据变更日志,系统异常日志等,我们希望把这些常规需求都集成到OSharp框架当中.有了内置的支持,在做开发的时候,只需要很简单的配置,就可以实现相关需求. 关于三类日志,这里先简要描述一下: 操作日志:粗略描述系统用户(如管理员.业务人员.会员等)对系统的业务

【开源】OSharp3.3框架解说系列(7.1):初始化流程概述

本文已同步到系列目录:OSharp快速开发框架解说系列 框架初始化 相对于OSharp 3.0,3.3版本最大的更新,就是从框架级别定义了初始化流程,对初始化功能进行了抽象与封装,不依赖于第三方实现,第三方实现仅作为可替换的服务实现方案存在. 例如,依赖注入功能中,接口与其实现类的映射配置,对象容器的构建,对象的解析获取,都将通过框架定义的API来完成,而Autofac,仅作为这些功能的实现方存在,如果不想使用Autofac,则可以很方便的切换成别的IoC组件. 具体的初始化功能是怎样抽象与定义