隐式类型局部变量的具体类型取决于初始化它的表达式。在一个局部变量声明时,前面加一个var关键字,这个局部变量就被叫做隐式类型局部变量。例如:

              var i = 0;
            var s = "Hello";
            var d = 1.0;
            var numbers = new int[] { 2, 4, 7, 8 };
            var order = new Dictionary<string, string>();

上面的隐式类型局部变量的声明等价于下面的显示类型声明:

              int i = 0;
            string s = "Hello";
            double d = 1.0;
            int[] numbers = new int[] { 2, 4, 7, 8 };
            Dictionary<string,string> order = new Dictionary<string, string>();

用隐式类型局部变量声明的本地变量声明要遵循下面的规则:

●声明的同时必须赋初始值。

●初始值必须是一个表达式。

●初始表达式必须是一个编译时类型,不能是null类型。

●局部变量声明不能包含多个声明。

●初始化不能引用声明的变量本身。

下面都是不正确的隐式类型局部变量声明:

            var x;                 //Error:声明时没有赋值
            var y = { 1, 2, 3 };   //Error:不允许是集合初始化器
            var z = null;          //Error:不能是null类型
            var u = x => x + 1;    //Error:Lamda表达式没有类型
            var d1 = 0.1, d2 = 0.2;//Error:一个var不能包含多个声明
            var v = v++;           //Error:初始表达式不能引用声明的变量本身

注意:除本地变量声明外,for语句,using语句,foreach语句也可以用隐式类型局部变量。例如: 

            using (var fs = new MemoryStream()) { }
            for (var i = 0; i < 100; i++){ }
            foreach (var num in numbers) { }

2. 扩展方法(Extension Methods)

 扩展方法是静态方法,它们只能被定义在非泛型,非嵌套的静态类里。下面是一个例子:

    public static class Extensions
    {
        public static Int32 ToInt32(this string s)
        {
            return Convert.ToInt32(s);
        }
        public static void ForEach<T>(this IEnumerable<T> enumer, Action<T> action)
        {
            foreach (var item in enumer)
            {
                action(item);
            }
        }
    }

扩展方法第一个参数要以this开头,表示要在紧跟this的类型上进行扩展。扩展方法具有静态方法的所有特征,并且,它能像调用实例方法那样进行调用,就像是“类型本身的方法”一样。下面的代码调用了扩展方法:

            var s = "20";
            var num = s.ToInt32();//用实例方法的语法,调用扩展方法
            var num2 = Extensions.ToInt32(s);//也可以像静态方法那样调用扩展方法

 方法调用的优先级:实例方法>内部命名空间的扩展方法>外部命名空间的扩展方法。例子:

public static class E
{
   public static void F(this object obj, int i) { }
   public static void F(this object obj, string s) { }
}
class A { }
class B
{
   public void F(int i) { }
}
class C
{
   public void F(object obj) { }
}
class X
{
   static void Test(A a, B b, C c) {
      a.F(1);            // E.F(object, int)
      a.F("hello");      // E.F(object, string)
      b.F(1);            // B.F(int)
      b.F("hello");      // E.F(object, string)
      c.F(1);            // C.F(object)
      c.F("hello");      // C.F(object)
   }
}

3. Lambda表达式(Lambda Expressions)

    C#2.0提供了匿名方法(anonymous methods)语法,它允许以“内联”的方式书写代码块,大大的简化了代码的编写。Lambda是对匿名方法语法的进一步简化,Where)的参数。

3.1 Lambda表达式

表达式在右边的 Lambda 表达式称为“Lambda 表达式”。 Lambda 表达式在构造表达式树(C# 和 Visual Basic)时广泛使用。 Lambda 表达式返回表达式的结果,并采用以下基本形式:

(input parameters) => expression

两个或更多输入参数由括在括号中的逗号分隔: 

(x, y) => x == y

 如果出现这种情况,您可以按以下示例中所示方式显式指定类型: 

(int x, string s) => s.Length > x

 使用空括号指定零个输入参数:

() => SomeMethod()

3.2 Lambda语句

Lambda 语句与 Lambda 表达式类似,只是语句括在大括号中:

(input parameters) => {statement;}

Lambda 语句的主体可以包含任意数量的语句;但是,实际上通常不会多于两个或三个语句。 

delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");

像匿名方法一样,Lambda 语句无法用于创建表达式树。 

3.3 带有标准查询运算符的 Lambda

许多标准查询运算符都具有输入参数,其类型是泛型委托的 Func<T, TResult> 系列的其中之一。它的定义的类型如下: 

public delegate TResult Func<TArg0, TResult>(TArg0 arg0)

 在调用下面的 Func 委托时,该委托将返回 true 或 false 以指示输入参数是否等于 5: 

Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false of course

 如果指定Expression<Func> 参数,Lambda 将编译为表达式树。

Count 方法: 

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);

 n) 的数量,这些整数除以 2 时余数为 1。

numbers 数组中在 9 左边的所有元素,因为 9 是序列中不满足条件的第一个数字: 

var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);

 >=) 混淆。 

var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);

 3.4 Lambda的类型推理

Customer 对象,这意味着您可以访问其方法和属性: 

customers.Where(c => c.City == "London");

 Lambda 的一般规则如下:

  • Lambda 包含的参数数量必须与委托类型包含的参数数量相同。

  • Lambda 中的每个输入参数必须都能够隐式转换为其对应的委托参数。

  • Lambda 的返回值(如果有)必须能够隐式转换为委托的返回类型。

Expression 类型。

3.5 Lambda表达式的类型范围

 下面的示例演示这些规则: 

 delegate bool D();
    delegate bool D2(int d);
    class LamdaDemo
    {
        D del;
        D2 del2;
        public void TestMethod(int input)
        {
            int k = 0;
            // Initialize the delegates with lambda expressions.
            // Note access to 2 outer variables.
            // del will be invoked within this method.
            del = () =>{k = 10;return k > input;};

            // del2 will be invoked after TestMethod goes out of scope.
            del2 = (n) => {return n == k;};

            // Output: k = 0 
            Console.WriteLine("k={0}", k);
            bool b = del();

            // Output: k = 10 b = True
            Console.WriteLine("k={0},b={1}", k, b);
        }
        public static void Demo()
        {
            LamdaDemo demo = new LamdaDemo();
            demo.TestMethod(5);

            // Prove that del2 still has a copy of
            // local variable j from TestMethod.
            bool result = demo.del2(10);

            // Output: True
            Console.WriteLine(result);
        }
    }

 下列规则适用于 Lambda 表达式中的变量范围: 

  • 捕获的变量将不会被作为垃圾回收,直至引用变量的委托超出范围为止。

  • 在外部方法中看不到 Lambda 表达式内引入的变量。

  • out 参数。

  • Lambda 表达式中的返回语句不会导致封闭方法返回。

  • continue 语句。

3.6 Lambda表达式与匿名方法的异同

  • 匿名方法可以完全省略参数列表,Lambda表达式至少要留一个()括号。
  • Lambda表达式的参数允许省略类型,匿名方法必须显示声明类型。
  • Lambda表达式的类容可以是一个表达式,也可以是一个语句块。匿名方法只能是一个语句块。
  • Lambda表达式的类容如果是一个表达式,可以转换成表达式树。
  • Lambda表达式和匿名方法都是C#语法层面的东西,对于CLR来说,它只认识被C#编译器编译后的delegate。

4. 对象和集合初始化器(Object and Collection Initializers)

对象初始化器允许在声明一个对象,并对对象进行初始化时,不用显示调用类型的构造函数。但要注意,对象初始化器要调用默认的构造函数,如果默认的构造函数被声明为private或protected将会引起编译错误。下面首先定义一个Student类:

    public class Student
    {
        public Student() { }//这是默认构造函数,注意它是public的
        public Student(string firstName, string lastName)
        {
            this.FirstName = firstName;
            this.LastName = lastName;
        }
        public int ID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public override string ToString()
        {
            return string.Format("{0}-{1}-{2}", FirstName, LastName ,ID);
        }
    }

然后可以像下面这样初始化对象,注意它们之间的区别:

        public static void Demo()
        {
            //调用的是有两个参数的构造函数
            Student student1 = new Student("zhang", "qiang");

            //用对象初始化器初始化对象,只指定FirstName和
            //LastName字段,它会调用默认构造函数
            Student student2 = new Student { FirstName = "zhang", LastName = "qiang" };

            //用对象初始化器初始化对象,只指定ID字段,
            //它会调用默认构造函数
            Student student3 = new Student { ID = 100 };

            //用对象初始化器初始化对象,指定全部的字段,
            //它会调用默认构造函数
            Student student4 = new Student { ID =116,FirstName ="zhang",LastName = "qiang"};

            Console.WriteLine(student1);
            Console.WriteLine(student2);
            Console.WriteLine(student3);
            Console.WriteLine(student4);
        }

需要注意的是:对象初始化器并不需要指定对象的全部字段。

注意,对象和对象之间用逗号隔开。 

            List<Student> students = new List<Student>()
            {
                new Student{ID =100,FirstName ="zhang",LastName = "qiang"},
                new Student{ID =101,FirstName ="wang",LastName = "li"},
                new Student{ID =102,FirstName ="qiao",LastName = "xin"}
            };

 5.匿名类型(Anonymous Types)

new 运算符和对象初始器创建匿名类型。

            var v = new { Amount = 100, Message = "Hello" };
            // Rest the mouse pointer over v.Amount and v.Message in the following
            // statement to verify that their inferred types are int and string.
            Console.WriteLine(v.Amount + v.Message);

select 子句中,以便返回数据源中每个对象的属性子集。

null、匿名函数或指针类型。 

这将导致在查询中返回较少数量的数据。 

Price。 

var productQuery = 
    from prod in products
    select new { prod.Color, prod.Price };

foreach (var v in productQuery)
{
    Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

 可通过将隐式类型局部变量与隐式类型数组相结合创建匿名的元素的数组,如下面的示例所示。 

            var anonArray = new[] { new { ID = 10, City = "beijing" }, new { ID = 12, City = "Shanghai" } };
            foreach (var e in anonArray)
            {
                Console.WriteLine(e.ID + e.City);
            }

 6.隐式类型数组(Implicitly Typed Arrays)

有关任何隐式类型变量的规则也适用于隐式类型的数组。

它的基本语法格式是: 

new[] {/*数组初始化器 */}

 下面是一些例子:

            var a = new[] { 1, 2, 3,4 };//int[]
            var b = new[] { "hello", null, "world" };//string[]
            //二维的int数组
            var c = new[]{
                new[]{1,2,3,4},
                new[]{5,6,7,8}
            };
            //string类型的锯齿数组
            var d = new[]{
                new[]{"Luca", "Mads", "Luke", "Dinesh"},
                new[]{"Karen", "Suma", "Frances"}
            };

var 关键字。 

            var contacts = new[]{
                new {Name = "Killy",PhoneNumbers=new []{"001-12345","002-25825"}},
                new {Name = "Jimmy",PhoneNumbers=new []{"123-45678"}}
            };

 7.查询表达式(Query Expressions)

查询表达式提供语言集成语法来支持查询。它类似于拥有关系和层级结构的查询语言,如SQL和XQuery。查询表达式是编程语言级别的语法,C#3.0和VB9.0都支持这样的语法,但CLR却并不知道,它只认识具体的类型和方法。查询表达式被语言编译器编译成CLR可以识别的方法,他们都定义在IEnumerable<T> 或 IQueryable<T>的一个静态类里,并且都是些扩展方法。这些扩展方法中的一部分和查询表达式一一对应,我们完全可以用这种方法调用的方式来书写查询,但很明显,查询表达式提供了清晰且易阅读的语法,简化了这种书写量。

group 子句的结果能够充当同一查询表达式中附加查询子句的源。它们会被翻译成CLR能识别的WhereSelectSelectManyJoinGroupJoinOrderByOrderByDescendingThenByThenByDescendingGroupBy, and Cast扩展方法。

下面是一个例子: 

        public static void Demo()
        {
            //Data Source
            int[] scores = { 90, 71, 82, 93, 75, 82 };

            //Query Expression
            IEnumerable<int> scoreQuery =//query variable
                from score in scores//required
                where score > 80// optional
                orderby score descending// optional
                select score;//must end with select or group

            // Execute the query to produce the results
            foreach (var score in scoreQuery)
            {
                Console.WriteLine(score);
            }            
        }
        //OutPut: 93 90 82 82

 只要该变量和数据源都没有修改,该变量都将产生相同的结果。

queryMajorCities2 都是查询变量: 

//Query syntax
IEnumerable<City> queryMajorCities =
    from city in cities
    where city.Population > 100000
    select city;


// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);

 查询表达式是Linq的基础,内容很丰富,这里不详细解释每个关键字的用法,只做简单的展示。想了解更加详细的内容,可以参看MSDN:http://msdn.microsoft.com/zh-cn/library/bb384065.aspx

8.表达式树(Expression Trees)

表达式树允许用Lambda表达式来表示一个数据结构,而不是执行代码。如果一个Lambda表达式可以转化成一个delegate类型D,那么它也可以转换成一个表达式树类型System.Query.Expression<D>。然而,当Lambda表达式转换成一个delegate时会生成一个delegate类型的可执行代码;转换成表达式树的时候,只会生成一颗发散的表达式的实例,它是一种高效透明的数据结构。

下面的代码展示了Lamba表达式如何转换成可执行代码(delegate)和一个数据结构(表达式树)。因为一个转换成了Func<int,int>,一个转换成了Expression<Func<int,int>>。 

Func<int,int> f = x => x + 1;                  // Code
Expression<Func<int,int>> e = x => x + 1;      // Data

 上面的代码中,delegate 参照一个方法,它返回 x + 1; 而表达式树 e 参照一个数据结构,它描述了一个表达式 x + 1.

9.自动实现属性(Automatically Implemented Properties)

客户端代码还可通过这些属性创建对象。 

    public class ParamInfo
    {
        public string Name { get; set; }//自动属性
        public string Value { get; set; }//自动属性
    }

 使用方式和一般属性相似:

var param = new ParamInfo { Name = "Text", Value = "ID" };

可以对自动属性设置访问级别,如构造一个只读属性的类型,可以将set访问器设为private,对象只能通过构造函数初始化值:

    public class ParamInfo
    {
        public string Name { get; private set; }//自动属性
        public string Value { get;private  set; }//自动属性
        //构造函数
        public ParamInfo(string name, string value)
        {
            this.Name = name;
            this.Value = value;
        }
    }

注意:自动属性可以简化语法,但并不是万能的,使用时需要注意,下面的场合不适合用自动属性。

  • 属性访问器中含有复杂逻辑。
  • 包含属性的类型需要序列化时。

 (全篇完)

相关文章: