通用扩展


  通用扩展的类名为 GenericExtension。这里提供了比较通用的扩展方法,大多是基于 System.Object 类型的扩展。

1、判断对象是否为空

  IsNullOrEmpty 方法可以判断任意类型的对象是否为空。它对对象进行null、DBNull,或是空字符串检测,如果是可枚举类型则检查有没有元素。

[TestMethod]
public void TestIsNullOrEmpty()
{
    var list = new List<string>();
    object obj = null;
    int? i = null;

    Assert.IsTrue(list.IsNullOrEmpty());
    Assert.IsTrue(obj.IsNullOrEmpty());
    Assert.IsTrue(i.IsNullOrEmpty());
}

2、尝试销毁对象资源

  TryDispose 方法会判断实例是否实现了 IDisposable 接口,如果实现则调用 Dispose 方法来释放其使用的资源。

💡 扩展阅读

  Fireasy 里提供了一个 DisposableBase 类,它实现了标准的 IDispose 模式。TryDispose 方法还有一个参数: disposing,一般在一个类重写 Dispose (bool disposing) 方法时,会传递该参数,它会告诉对象是通过 Dispose 方法调用还是通过析构函数调用。

protected bool Dispose(bool disposing)
{
    db.TryDispose(disposing);
}

3、判断对象是否为空,非空时操作对象

  AssertNotNull 方法用于判断对象是否为空,如果不为空,则返回对象的属性,或调用方法。

  比如判断可空的日期类型,并输出字符串:

[TestMethod]
public void TestAssertNotNull()
{
    DateTime? date1 = null;
    DateTime? date2 = DateTime.Parse("2009-1-1");
    Assert.IsNull(date1.AssertNotNull(s => s.ToShortDateString()));
    Assert.AreEqual("2009-01-01", date2.AssertNotNull(s => s.ToString("yyyy-MM-dd")));
}

  注:在C#6语法中,可以使用?来进行判断,所实现的效果和以上代码是一致的,如:

[TestMethod]
public void TestAssertNotNull()
{
    DateTime? date1 = null;
    DateTime? date2 = DateTime.Parse("2009-1-1");
    Assert.IsNull(date1?.ToShortDateString());
    Assert.AreEqual("2009-01-01", date2?.ToString("yyyy-MM-dd"));
}

4、As 和 Is

  As 和 Is 方法判断实例是否可转换为指定的类型。As 和 Is 方法的效果与 as 和 is 关键字的效果一样,只是 As 方法可以在结果为 true 的情况下,通过委托调用其方法。

public interface IInvokable
{
    void Invoke();
}

public class GenericInvoker : IInvokable
{
    public void Invoke()
    {
        Console.WriteLine("hello world");
    }
}

public class NoGenericInvoker
{
}

[TestMethod]
public void TestAs()
{
    var obj1 = new GenericInvoker();
    obj1.As<IInvokable>(s => s.Invoke());

    var obj2 = new NoGenericInvoker();
    obj2.As<IInvokable>(s => s.Invoke(), () => Console.WriteLine("obj2不是 IInvokable 类型"));
}

  注:在C#7语法中,可以使用 is 操作符来判断变量是否成立,并转换成对应类型的变量,如:

[TestMethod]
public void TestIs()
{
    var obj1 = new GenericInvoker();
    if (obj1 is IInvokable invoker) { invoker.Invoke(); }
}

5、安全的ToString

  一般地,调用 ToString 方法时,如果对象为空,则会引发 “未将对象引用设置到对象的实例” 这样的异常。ToStringSafely 方法会判断对象是否为空,不为空才调用 ToString 方法。

public class GenericData
{
    public override string ToString()
    {
        return string.Empty;
    }
}

[TestMethod]
public void TestToString()
{
    GenericData data = null;

    //使用ToString()将抛出异常
    Assert.AreEqual(string.Empty, data.ToStringSafely());
}

6、转换类型

  To 方法不像调用 Convert.ChangeType 方法那么简单地处理值类型,它加入了一些智能的判断,比如可以将 1、0、"True"、"False" 等转换为布尔类型,可支持枚举类型与数字、字符串之间的转换。如果源对象为空,可以返回指定的默认值。

[TestMethod]
public void TestTo()
{
    Assert.AreEqual(true, "true".To<bool>());
    Assert.AreEqual(false, "0".To<bool>());
    Assert.AreEqual(2332.4m, "2332.4".To<decimal>());
    Assert.AreEqual(34, "34d".To<int>(34));
}

  对于原生类型,如 List、Dictionary、Dynamic,To 方法可以将对象转换为其他的类型,它在目标类型中寻找相同的属性进行复制。即使转换的类型是接口,它也会动态编译一个类型来实现该接口,以便能够最终转换。

public interface IPeople
{
    string Name { get; set; }
}

[TestMethod]
public void TestObjectTo()
{
    var source = new List<object>();
    source.Add(new { Name = "huangxd" });
    source.Add(new { Name = "liming" });

    var dest = source.To<List<IPeople>>();
    Assert.AreEqual(2, dest.Count);
    Assert.AreEqual("huangxd", dest[0].Name);
}

  To 方法是 ToType 方法的泛型版本,如果已知 Type 类型则使用 ToType 方法。

  你可能使用过 AutoMapper 吧,To 方法也可以使用类似的映射器来进行对象转换。

[TestMethod]
public void TestMapTo()
{
    var mapper = new ConvertMapper<Data1, Data2>()
        .Map(s => s.Description, s => s.Name + " test")
        .Map(s => s.Other, s => "other");

    var data1 = new Data1 { Name = "fireasy" };
    var data2 = data1.To(mapper);
    Assert.AreEqual("fireasy test", data2.Description);
    Assert.AreEqual("other", data2.Other);
}

7、范围判断

  IsBetween 方法用于判断指定的值是否在最小值和最大值之间,它通过提供的一个 IComparer 实例来完成比较。

8、对象扩展

  Extend方 法类似 jQuery 中的 Extend 方法,可以对源对象进行属性扩展,构造新的动态对象。

[TestMethod]
public void TestExtend()
{
    object obj = new { Name = "fireasy", Sex = 0 };
    dynamic obj1 = obj.Extend(new { Address = "kunming" });

    Assert.AreEqual("fireasy", obj1.Name);
    Assert.AreEqual("kunming", obj1.Address);
}

  Extend 方法扩展的新对象是动态类型的,然而很多时候更希望扩展的新对象是某一特定类型,因此 Extend 方法还提供了一个泛型版本 ExtendAs,泛型参数即新对象的类型。

public class Data1
{
    public string Name { get; set; }
}
public class Data2
{
    public string Sex { get; set; }

    public int Age { get; set; }
}

[TestMethod]
public void TestExtendAs()
{
    var d1 = new Data1 { Name = "fireasy" };
    var d2 = d1.ExtendAs<Data2>(new { Age = 12 });
    var d3 = d2.ExtendAs<Data2>(new { Sex = "男" });
    Assert.AreEqual(12, d3.Age);
    Assert.AreEqual("男", d3.Sex);
}

9、转为动态对象

  ToDynamic 将一个对象转换为动态对象。

[TestMethod]
public void TestToDynamic()
{
    dynamic obj = new { Name = "fireasy" }.ToDynamic();
    var dic = obj as IDictionary<string, object>;
    Assert.AreEqual("fireasy", obj.Name);
    Assert.AreEqual("fireasy", dic["Name"]);
}

💡 扩展阅读

  Fireasy 里提供了一个 DynamicExpandoObject 类型,ToDynamic 转换后的对象属于此类型,因此可使用 IDictionary<string, object> 进行转换。