跟小静读CL奥迪Q7,扩充方法知多少

最近加班好累a...题外话哈

当我们想为一个现有的类型添加一个方法的时候,有两种方式:一是直接在现有类型中添加方法;但是很多情况下现有类型都是不允许修改的,那么可以使用第二种方式,基于现有类型创建一个子类,然后在子类中添加想要的方法。

对于一些现成的类,如果我们想添加一些新的方法来完善功能,但是不想改变已有的封装,也不想使用派生类,那么该怎么办呢?这里我们可以使用扩展方法。

枚举不用多说,介绍下扩展方法:扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。[当然是从msdn拷贝的咯,详情请见~]

当C# 2.0中出现了静态类之后,对于上面的问题,我们也可以创建静态工具类来实现想要添加的方法。这样做可以避免创建子类,但是在使用时代码就没有那么直观了。


扩展方法需定义在静态类中,并且方法本身也应声明为静态的,第一个参数指定该方法作用于哪个类型,并且该参数以 跟小静读CL奥迪Q7,扩充方法知多少。this 修饰符为前缀。注意,与接口或类方法具有相同名称和签名的扩展方法永远不会被调用。

其实,上面的方法都不是很好的解决办法。在C# 3.0中出现了扩展方法,通过扩展方法我们可以直接在一个现有的类型上"添加"方法。当使用扩展方法的时候,可以像调用实例方法一样的方式来调用扩展方法。

一见钟情--初识扩展

扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。

我们首先来看个例子,有个直观的认识。一个现有的类User:

public class User
  {
      string _name;
      public User(string Name)
      {
          _name = Name;
      }
      public string Name
      {
          get { return _name; }
          set { this._name = value; }
      }
  }

现在我们想要增加一个方法来显示user信息,又不想修改User类。使用扩展方法

     图片 1

扩展方法是一种比较特殊的使用,我们可以定义静态方法,然后在目标类型中以实例方法的语法进行调用。有了上面的定义后,当使用User实例时,会产生相应的智能感知,而且会提示是扩展方法。

    图片 2

调用结果

User user = null;//实例为null
user.DisplayName();
user = new User("小静");//实例不为Null
user.DisplayName();
Console.Read();

图片 3

查看ILDASM.exe,我们看到定义扩展方法后,Extension类会添加一个ExtensionAttribute标记。

图片 4

举个栗子:

扩展方法的使用

扩展方法的创建和使用还是相对比较简单的。

了解扩展

怎样定义扩展方法?

  • 定义一个静态类,名称不限;
  • 定义静态方法,第一个参数类型为要扩展的目标类型;为了表明是扩展方法,还要给第一个参数添加this关键字。

编译过程识别顺序?

在上面的例子中,调用语句为user.DisplayName();,那么编译器的检查过程:

  • 首先检查变量类型User及其基类是否定义了DisplayName()实例方法,如果存在则会生成调用该方法的IL代码;
  • 如果不存在,则会继续检查静态类中是否存在一个名为DisplayName、第一个参数为User而且带有this关键字的静态方法,如果存在就会生成相应的IL代码。
  • 如果仍然不存在,则会产生编译错误。
 1     public enum EnumWeekday
 2     {
      
       None,  // 不好意思,强行植入None,记住它会有用的

 3         Monday,
 4 
 5         TuesDay,
 6 
 7         星期三,    // Unicode就是这么自信
 8 
 9         Thursday,
10 
11         Friday,
12     }
13 
14     // 第二个方法不是扩展方法,一般只将扩展方法放在一个类中
15     // 当然可以将其声明为扩展方法,但不建议
16     static class ExternFunc
17     {
18         /// <summary>
19         /// EnumWeekday枚举的扩展方法
20         /// </summary>
21         public static string ToChinese(this EnumWeekday day)
22         {
23             switch (day)
24             {
25                 case EnumWeekday.Monday:
26                     return "星期一";
27 
28                 case EnumWeekday.TuesDay:
29                     return "星期二";
30 
31                 default:
32                     return day.ToString();
33             }
34         }
35 
36         /// <summary>
37         /// 字符串转枚举
38         /// </summary>
39         public static EnumWeekday String2EnumWeekday(string day)
40         {
41             switch (day)
42             {
43                 case "星期一":
44                     return EnumWeekday.Monday;
45 
46                 default:
47                     return EnumWeekday.Friday;  // 皮一下,反正周末还不是要加班-_-
48             }
49         }
50     }

声明扩展方法

相比普通方法,扩展方法有它自己的特征,下面就来看看怎么声明一个扩展方法:

  • 它必须在一个非嵌套、非泛型的静态类中(所以扩展方法一定是静态方法)
  • 它至少要有一个参数
  • 第一个参数必须加上this关键字作为前缀,且第一个参数类型也称为扩展类型(extended type),表示该方法对这个类型进行扩展

  • 第一个参数不能用其他任何修饰符(比如out或ref)

  • 第一个参数的类型不能是指针类型

根据上面的要求,我们给int类型添加了一个扩展方法,用来判断一个int值是不是偶数:

图片 5;)

namespace ExtentionMethodTest
{
    public static class ExtentionMethods
    {
        public static bool IsEven(this int num)
        {
            return num % 2 == 0;
        }
    }

    class Program
    {
        static void Main(string[] args)
        { 
            int num = 10;
            //直接调用扩展方法
            Console.WriteLine("Is {0} a even number? {1}", num, num.IsEven());
            num = 11;
            //直接调用扩展方法
            Console.WriteLine("Is {0} a even number? {1}", num, num.IsEven());
            //通过静态类调用静态方法
            Console.WriteLine("Is {0} a even number? {1}", num, ExtentionMethods.IsEven(num));

            Console.Read();         
        }
    }
}

图片 6;)

虽然这个例子非常简单,但却演示了扩展方法的使用。

正果守则--扩展规则

  • 扩展方法必须在非泛型静态类中声明,类名无限制,例如Extension类的名字可以任意修改后,都能正常调用扩展方法。扩展方法至少有一个参数,且第一个参数是目标扩展类型且用this关键字标识。
  • 扩展方法所在的静态类不能嵌套在另外一个类中。像下面这样定义会产生编译错误。

         图片 7

        图片 8

  • 扩展方法可以在不同的静态类中定义,所以不同的静态类中可能出现同名的扩展方法,编译器纠结了不知道该如何调用,只好产生编译错误。例如

          图片 9

此时,我们不能再用实例方法的语法来调用了,只能用静态方法语法调用。

            user = new User("小静");//实例不为Null
            Extension.DisplayName(user);
            Extension1.DisplayName(user);

  • 派生类也继承了扩展方法, 例如我们定义派生类Student:

public class Student:User{}

它的智能感知,也包含了User类的扩展方法。

     图片 10

所以在定义扩展方法时要比较注意,不能过多使用。基类中使用过多的扩展方法后,也许会使派生类中产生一些多余的智能感知。

  • 版本问题。如果某一天向目标扩展类定义了同名的方法DisplayName后,调用时就会覆盖之前的扩展方法,改变原先程序的行为。所以扩展方法要慎重使用。

         图片 11

        图片 12

  • 扩展方法和实例方法虽然语法看上去一样,但它俩有一个重要的区别,看下面这个调用。

User user = null;//实例为null
user.DisplayName();

实例方法调用时,对象不能为null,会产生运行时错误。

扩展方法世界上是调用静态方法,所以调用它的实例对象可以为Null。

  • 扩展接口?

除了为类型扩展方法外,还可以为接口定义扩展方法。例如

图片 13

调用过程:

new[] { "AA", "BBB", "CCCC" }.ShowItems();
"Cathy".ShowItems();
Console.Read();

上面已介绍,像实例方法一样调用扩展方法就欧克了--EnumWeekday friday = EnumWeekday.Friday; Debug.WriteLine("今天是{0}", friday.ToChinese());

调用扩展方法

通过上面的例子可以看到,当调用扩展方法的时候,可以像调用实例方法一样。这就是我们使用扩展方法的原因之一,我们可以给一个已有类型"添加"一个方法。

既然扩展方法是一个静态类的方法,我们当然也可以通过静态类来调用这个方法。

通过IL可以看到,其实扩展方法也是编译器为我们做了一些转换,将扩展方法转化成静态类的静态方法调用

图片 14;)

IL_001f: nop
IL_0020: ldc.i4.s 11
IL_0022: stloc.0
IL_0023: ldstr "Is {0} a even number? {1}"
IL_0028: ldloc.0
IL_0029: box [mscorlib]System.Int32
IL_002e: ldloc.0
//直接调用扩展方法
IL_002f: call bool ExtentionMethodTest.ExtentionMethods::IsEven(int32)
IL_0034: box [mscorlib]System.Boolean
IL_0039: call void [mscorlib]System.Console::WriteLine(string, object, object)
IL_003e: nop
IL_003f: ldstr "Is {0} a even number? {1}"
IL_0044: ldloc.0
IL_0045: box [mscorlib]System.Int32
IL_004a: ldloc.0
//通过静态类调用静态方法
IL_004b: call bool ExtentionMethodTest.ExtentionMethods::IsEven(int32)
IL_0050: box [mscorlib]System.Boolean
IL_0055: call void [mscorlib]System.Console::WriteLine(string, object, object)
IL_005a: nop
IL_005b: call int32 [mscorlib]System.Console::Read()
IL_0060: pop
IL_0061: ret

图片 15;)

有了扩展方法,当调用扩展方法的时候,我们就像是调用一个实例方法。但是,我们应该从两个角度看这个问题:

  • 通过扩展方法,可以使一些方法的调用变得更加通俗易懂,与实例的关系看起来更协调。就例如,"num.IsEven()"这种写法。

    • 基于这个原因,可以考虑把代码中静态工具类中的一些方法变成扩展方法
  • 当然正是由于扩展方法的调用跟实例方法一样,所以想要一眼就看出一个方法是不是扩展方法不那么容易

    • 其实在VS中还是很好辨别的,对于上面的例子,在VS中放上鼠标,就可以看到"(extention) bool int.IsEven()"
图片 16

扩展方法是怎样被发现的

知道怎样调用扩展方法是我们前面部分介绍的,但是知道怎样不调用扩展方法同样重要。下面就看看编译器怎样决定要使用的扩展方法。

编译器处理扩展方法的过程:当编译器看到一个表达式好像是调用一个实例方法的时候,编译器就会查找所有的实例方法,如果没有找到一个兼容的实例方法,编译器就会去查找一个合适的扩展方法;编译器会检查导入的所有命名空间和当前命名空间中的所有扩展方法,并匹配变量类型到扩展类型存在一个隐式转换的扩展方法。

当编译器查找扩展方法的时候,它会检查System.Runtime.CompilerServices.ExtensionAttribute属性来判断一个方法是否是扩展方法

图片 17

看到了编译器怎么处理扩展方法了,那么就需要了解一下使用扩展方法时要注意的地方了。

扩展方法使用的注意点:

  • 实例方法的优先级高于扩展方法,当有扩展方法跟实例方法签名一致的时候,编译器不会给出任何警告,而是默认调用实例方法

  • 如果存在多个适用的扩展方法,它们可以应用于不同的扩展类型(使用隐式转换),那么通过在重载的方法中应用的"更好的转换"规则,编译器会选择最合适的一个

  • 在扩展方法的调用中,还有一个规则,编译器会调用最近的namespace下的扩展方法

下面看一个例子,通过这个例子来更好的理解编译器处理扩展方法时的一些注意点:

图片 18;)

namespace ExtentionMethodTest
{
    using AnotherNameSpace;
    public static class ExtentionMethods
    {
        public static void printInfo(this Student stu)
        {
            Console.WriteLine("printInfo(Student) from ExtentionMethodTest");
            Console.WriteLine("{0} is {1} years old", stu.Name, stu.Age);
        }

        public static void printInfo(this object stu)
        {
            Console.WriteLine("printInfo(object) from ExtentionMethodTest");
            Console.WriteLine("{0} is {1} years old", ((Student)stu).Name, ((Student)stu).Age);
        }
    }

    public class Student
    {
        public string Name { get; set; }
        public int Age { get; set; }

        //实例方法
        //public void printInfo()
        //{
        //    Console.WriteLine("{0} is {1} years old", this.Name, this.Age);
        //}
    }

    class Program
    {
        static void Main(string[] args)
        {
            Student wilber = new Student { Name = "Wilber", Age = 28 };
            //当实例方法printInfo存在的时候,所有的扩展方法都不可见
            //此时调用的是实例方法
            //wilber.printInfo();

            //当注释掉实例方法后,下面代码会调用最近的命名空间的printInfo方法
            //同时下面语句会选择“更好的转换”规则的扩展方法
            //printInfo(Student) from ExtentionMethodTest
            //Wilber is 28 years old
            wilber.printInfo();

            //当把wilber转换成object类型后,会调用printInfo(this object stu)
            //printInfo(object) from ExtentionMethodTest
            //Wilber is 28 years old
            object will = wilber;
            will.printInfo();

            Console.Read();
        }
    }
}

namespace AnotherNameSpace
{
    using ExtentionMethodTest;
    public static class ExtentionClass
    {
        public static void printInfo(this Student stu)
        {
            Console.WriteLine("printInfo(Student) from AnotherNameSpace");
            Console.WriteLine("{0} is {1} years old", stu.Name, stu.Age);
        }
    }
}

图片 19;)

结局才是开始

这篇对扩展方法的介绍到这里算是happy ending了吧。其实说起这个话题,鹤冲天 兄台的研究就深入多了。我就当是抛砖引玉了,大家有兴趣的话学习他的系列c# 扩展方法奇思妙用

扩展方法好资料: 

  • MSDN: 扩展方法(C# 编程指南)

  • 博客园: 鹤冲天 c# 扩展方法奇思妙用

空引用上调用扩展方法

当我们在空引用上调用实例方法是会引发NullReferenceException异常的。

但是,我们可以在空引用上调用扩展方法。

看下面的例子,我们可以判断一个对象是不是空引用。

图片 20;)

namespace ExtentionMethodTest
{
    public static class NullUitl
    {
        public static bool IsNull(this object o)
        {
            return o == null;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            object x = null;
            Console.WriteLine(x.IsNull());
            x = new object();
            Console.WriteLine(x.IsNull());

            Console.Read();
        }
    }
}

图片 21;)

通过上面的例子可以看到,即使引用为空,"x.IsNull()"仍然能够正常执行。

根据我们前面介绍的扩展方法的工作原理,其实上面的调用会被编译器转换为静态方法的调用"NullUitl.IsNull(x)"(可以查看IL代码验证),这也就解释了为什么空引用上可以调用扩展方法。

总结

本文介绍了扩展方法的使用以及工作原理,其实扩展方法的本质就是通过静态类调用静态方法,只不过是编译器帮我们完成了这个转换。

然后还介绍了编译器是如何发现扩展方法的,以及使用扩展方法时要注意的地方。了解了编译器怎么查找扩展方法,对编写和调试扩展方法都是有帮助的。

 

出处:

==============================================================================================

      前言:上篇 序列化效率比拼——谁是最后的赢家Newtonsoft.Json 介绍了下序列化方面的知识。看过Demo的朋友可能注意到了里面就用到过泛型的扩展方法,本篇打算总结下C#扩展方法的用法。博主打算分三个层面来介绍这个知识点,分别是:.Net内置对象的扩展方法、一般对象的扩展方法、泛型对象的扩展方法。

     什么是扩展方法?回答这个问题之前,先看看我们一般情况下方法的调用。类似这样的通用方法你一定写过:

图片 22;)

        static void Main(string[] args)
        {

            string strRes = "2013-09-08 14:12:10";
            var dRes = GetDateTime(strRes);
        }


        //将字符串转换为日期
        public static DateTime GetDateTime(string strDate)
        {
            return Convert.ToDateTime(strDate);
        }

        //得到非空的字符串
        public static string GetNotNullStr(string strRes)
        {
            if (strRes == null)
                return string.Empty;
            else
                return strRes;
        }

图片 23;)

或者在项目中有一个类似Utils的工具类,里面有多个Helper,例如StringHelper、XmlHelper等等,每个Helper里面有多个static的通用方法,然后调用的时候就是StringHelper.GetNotNullStr("aa");这样。还有一种普通的用法就是new 一个对象,通过对象去调用类里面的非static方法。反正博主刚开始做项目的时候就是这样写的。后来随着工作经验的累积,博主看到了扩展方法的写法,立马就感觉自己原来的写法太Low了。进入正题。

 

1、.Net内置对象的扩展方法

.Net内部也有很多定义的扩展方法,例如我们Linq常用的Where(x=>x==true)、Select()等等。当你转到定义的时候你很容易看出来:public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)。当然我们也可以给.Net对象新增扩展方法,比如我们要给string对象加一个扩展方法(注意这个方法不能和调用的Main方法放在同一个类中):

图片 24;)

        public static string GetNotNullStr(this string strRes)
        {
            if (strRes == null)
                return string.Empty;
            else
                return strRes ;
        }

图片 25;)

然后在Main方法里面调用:

        static void Main(string[] args)
        {
            string strTest = null;
            var strRes = strTest.GetNotNullStr();
        }

简单介绍:public static string GetNotNullStr(this string strRes)其中this string就表示给string对象添加扩展方法。那么在同一个命名空间下面定义的所有的string类型的变量都可以.GetNotNullStr()这样直接调用。strTest.GetNotNullStr();为什么这样调用不用传参数,是因为strTest就是作为参数传入到方法里面的。你可以试试。使用起来就和.Net framework定义的方法一样:

图片 26

 

     当然除了string,你可以给.Net内置的其他对象加扩展方法,例如给DataGridViewRow的扩展方法:

图片 27图片 28

//DataGridViewRow的扩展方法,将当前选中行转换为对应的对象
        public static T ToObject<T>(this DataGridViewRow item) where T:class
        {
            var model = item.DataBoundItem as T;
            if (model != null)
                return model;
            var dr = item.DataBoundItem as System.Data.DataRowView;
            model = (T)typeof(T).GetConstructor(new System.Type[] { }).Invoke(new object[] { });//反射得到泛型类的实体
            PropertyInfo[] pro = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
            Type type = model.GetType();
            foreach (PropertyInfo propertyInfo in pro)
            {
                if (Convert.IsDBNull(dr[propertyInfo.Name]))
                {
                    continue;
                }
                if (!string.IsNullOrEmpty(Convert.ToString(dr[propertyInfo.Name])))
                {
                    var propertytype = propertyInfo.PropertyType;
                }
            }
            return model;
        }

View Code

这样看上去就像在扩展.Net Framework。有没有感觉有点高大上~

 

2、一般对象的扩展方法

     和Framework内置对象一样,自定义的对象也可以增加扩展方法。直接上示例代码:

    public class Person
    {
        public string Name { set; get; }
        public int Age { set; get; }
    }

图片 29;)

        //Person的扩展方法,根据年龄判断是否是成年人
        public static bool GetBIsChild(this Person oPerson)
        {
            if (oPerson.Age >= 18)
                return false;
            else
                return true;
        }

图片 30;)

Main方法里面调用:

var oPerson1 = new Person();
oPerson1.Age = 20;
var bIsChild = oPerson1.GetBIsChild();        

和string扩展方法类似,就不多做解释了。

 

3、泛型对象的扩展方法

      除了上面两种之外,博主发现其实可以定义一个泛型的扩展方法。那么,是不是所有的类型都可以直接使用这个扩展方法了呢?为了保持程序的严谨,下面的方法可能没有实际意义,当开发中博主觉得可能存在这种场景:

图片 31;)

public static class DataContractExtensions
{
  //测试方法
  public static T Test<T>(this T instance) where T : Test2
  {
       T Res = default(T);
       try
       {
           Res.AttrTest = instance.AttrTest.Substring(0,2);
           //其他复杂逻辑...


      }
      catch
      { }
      return Res;
  }

}

public class Test2
{
  public string AttrTest { set; get; }
}

图片 32;)

 

使用扩展方法有几个值得注意的地方:

(1)扩展方法不能和调用的方法放到同一个类中

(2)第一个参数必须要,并且必须是this,这是扩展方法的标识。如果方法里面还要传入其他参数,可以在后面追加参数

(3)扩展方法所在的类必须是静态类

(4)最好保证扩展方法和调用方法在同一个命名空间下

 

      可能你第一次使用这个会觉得很别扭。你也许会说扩展方法和我以前用的static方法无论从代码实现还是算法效率都差不多嘛,是的!确实差不多,但使用多了之后会发现它确实能帮你省去很多代码。

 

出处:

===========================================================================================

另外参考微软的说明文档:

扩展方法(C# 编程指南)

如何:实现和调用自定义扩展方法(C# 编程指南)

如何:为枚举创建新方法(C# 编程指南)

本文由星彩网app下载发布于计算机编程,转载请注明出处:跟小静读CL奥迪Q7,扩充方法知多少

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。