C#2008与.NET 3.5 高级程序设计读书笔记(13)-- C#3.0的语言功能
1.具有隐式类型的局部变量var
在c#3.0里允许我们这样定义变量
var age = 10;
var name = "james";
var time = DateTime.Now;
var books = new string[]{"aa","bb"};
但是也仅此而已,var只允许我们定义c#编译器通过上下文推断出类型的变量。
var x; // 错误,没有用来推断类型的初始化器
var y = { 1, 2, 3 }; // 错误,不允许使用集合初始化器
var z = null; // 错误,不允许出现空类型
像这样也是不允许的
var a= 10;
a= "james";
我猜想var的出现其实完全是为了配合匿名类型而出现的。
Console.WriteLine(a.Name);
Console.WriteLine(a.Age);
在linq中应用也比较多
var queryLowNums =
from num in numbers
where num < 5
select num;
foreach (var s in queryLowNums)
{
Console.Write(s.ToString() + " ");
}
也就是说对象是匿名类型,或者对象是难以预测的类型的时候。
像这样的代码var age = 10;还是少写为好,一是类型安全,再是也为代码阅读尽量少造成障碍。
2.自动属性
public int ID { get; protected set; }
public string Name { get; set; }
这是接口内声明的属性么?
no,这也可以是类的属性,自动属性。
如果说c#3.0最大的改变是什么,那就是编码方式更人性化,程序员可以变的更懒。自动属性也是这一特征的具体表现。
对比两段代码
C# 2.0
{
private int _id;
private string _name;
public int ID
{
get
{
return _id;
}
protected set
{
_id = value;
}
}
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public a(int id)
{
this._id = id;
}
}
C# 3.0
{
public int ID { get; protected set; }
public string Name { get; set; }
public b(int id)
{
this.ID = id;
}
}
在c#中是推荐使用属性来代替public 变量的。我们在项目中也尽量将需要公开的字段定义为属性,代之而来的就是上面代码的冗繁。不管你对这个属性有没有特殊处理,都需要先定义一个私有变量,并将其在get,set方法中返回或赋值。使得代码量大量增加,阅读难度也增加。
随3.0而来的自动属性使我们可以方便的定义一个简单属性(不需要对field进行特殊处理的),他所使用的私有变量由系统来生成。工作量减少,阅读难度降低。
3.扩展方法
当我们需要对已有类的功能进行扩展时,我们通常会想到继承,继承已有类,然后为其加入新的行为。而C# 3.0中加入的扩展方法(Extension Methods)特性,则提供了另一种实现功能扩展的方式。
扩展方法是一种特殊的静态方法,它定义在一个静态类中,但可以在其他类的对象上像调用实例方法那样进行调用。因此,通过扩展方法,我们就可以在不修改一个类型的前提下对一个类型进行功能上的扩充,这种方法并不会产生新的类型,而是采用向已有类中加入新方法的方式来完成功能扩展;同时,也可以将一些近似的类型中近似的功能同一实现在一个类中,便于阅读和维护。从最终效果上看,扩展方法使得扩展一个现有类型和构造一个具有附加方法的类型变成了现实。
public static 返回类型 扩展方法名(this 要扩展的类型 参数名称 [,扩展方法参数列表])
比如,string类型有一个IsNullOrEmpty方法,用于测试某字符串是否为null或空。类似地,有时我们还需要一个方法,来测试某字符串是否为null、空字符串或者包含的字符都是空白字符。传统的一种方法是在像StringHelper这样的类中添加一个IsBlank方法:
public static class StringHelper
{
public static bool IsBlank(string s)
{
if (string.IsNullOrEmpty(s)) { return true; }
return (s.Trim().Length == 0);
}
}
在使用时,需要这样:
string s = null;
Assert.IsTrue(StringHelper.IsBlank(s));
这样看来,在完成所需功能时,string类型的实例需要另一个类StringHelper的帮助,显得不甚简洁。使用扩展方法,则变成这样:
public static class StringExtension
{
public static bool IsBlank(this string s)
{
if (string.IsNullOrEmpty(s)) { return true; }
return (s.Trim().Length == 0);
}
}
// 使用
string s = null;
Assert.IsTrue(s.IsBlank());
小节:我们希望向一个类添加成员。我们可以直接修改该类,但需要重新编译和部署,有时候可能代价较高;或许可以考虑继承和包装该类,但是存在于上面方法类似的问题;我们甚至不能修改或继承该类,比如string类或第三方组件中的某些类。这时使用扩展方法就可以比较柔和地注入所需的功能了。
4.对象和集合初始化器
在C#3.0中,一个对象创建表达式可以包含一个对象或集合初始化器,用于初始化新创建的对象的成员或新创建的集合的元素。
对象创建表达式:
new type (argument-list(可选)) 对象或集合初试化器(可选)
new type 对象或集合初试化器
(1)引入对象初始器
在传统的C#中,我们常用两种方法来对一个类(或者结构体)进行初始化,一是采用带有参数的构造函数,另一种则是不采用构造函数,或者构造函数中并没有实际地对字段进行赋值,而是在申请了类的实例之后,手动对它的公共属性进行赋值。下面是一个二维空间的几何点例子:
public class Point
{
private int xPos, yPos;
//缺省的构造函数
public Point()
{
}
public Point(int x, int y)
{
xPos = x;
yPos = y;
}
public int X
{
get { return xPos; }
set { xPos = value; }
}
public int Y
{
get { return yPos; }
set { yPos = value; }
}
public override string ToString()
{
return string.Format("[{0}, {1}]", xPos, yPos);
}
}
对于这个类,按照一般方法,我们会这样来初始化它:
//调用自定义的构造函数
Point p = new Point(100,200);
//或者手动指定每个属性
Point p1 = new Point();
p1.X = 100;
p1.Y = 200;
现在我们采用类初始化器的C# 3.0代码则可以写成下面的样子:
var p1 = new Point { X = 100, Y = 200 };
Point p = new Point { X = 100, Y = 200 };
(2)初始化内部类型
我们来看这样一个实际例子。我们需要定义一个Rectangle类,使用Point类型来代表左上角和右下角两个点的坐标,代码如下所示:
public class Rectangle
{
private Point topLeft = new Point();
private Point bottomRight = new Point();
public Point TopLeft
{
get { return topLeft; }
set { topLeft = value; }
}
public Point BottomRight
{
get { return bottomRight; }
set { bottomRight = value; }
}
public override string ToString()
{
return string.Format("[TopLeft: {0}, {1}, BottomRight: {2}, {3}]",
topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y);
}
}
好了,现在我们就可以使用对象初始化语法,创建一个Rectangle的实例并且将它内部的点设置如下:
Rectangle myRect = new Rectangle
{
TopLeft = new Point { X = 100, Y = 100 },
BottomRight = new Point { X = 200, Y = 200 }
};
在最后,为了能够让读者更好地进行对比学习,我们再给出它的原始调用方法:
Rectangle myRect = new Rectangle();
Point p1 = new Point();
p1.X = 100;
p1.Y = 100;
myRect.TopLeft = p1;
Point p2 = new Point();
p2.X = 200;
p2.Y = 200;
myRect.BottomRight = p2;
(3).集合初始化器
一个集合初始化器由一系列的元素初始化器构成,包围在{和}记号之间,并使用逗号进行分隔。每个元素初始化器指定一个元素,该元素将被添加到待初始化的集合对象中。为了避免与成员初始化器混淆,元素初始化器不能是赋值表达式。如下面的例子所示:
// 初始化一个普通数组
int[] myIntArray = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 初始化一个int型泛型List<>
List<int> myGenericList = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 下面这句是非法的,ArrayList没有实现ICollection<T>接口
ArrayList myList = new ArrayList { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
现在我们把前面用到的Point类应用进来,比如:
static void Main()
{
List<Point> myPointList = new List<Point>
{
new Point {X = 10, Y = 20},
new Point {X = 100, Y = 200},
new Point {X = 1000, Y = 2000}
};
foreach (var p in myPointList)
{
Console.WriteLine(p);
}
}
5.匿名类型
有时候,我们需要封装一组数据,只有数据,没有方法,并且只用于当前程序,不需要在项目间重用,这时候,如果是传统的使用类来封装的话,大概会是下面这种样子:
internal class SomeData
{
//定义一系列私有成员变量
//创建属性来封装每一个数据成员
//重写方法的ToString()来输出每一个数据成员
//重写GetHashCode() 及Equals()来实现基于值的相等性
}
这个虽然不难,但如果数据量比较多,显然写起来就比较麻烦了。因此,C#3.0以后提供了一个叫匿名类型的机制,来简化这一任务。如下:
var Me = new { Name = "Engine", Age = 26 };
Console.WriteLine("Name:" + Me.Name + "Age:" + Me.Age.ToString());
在LINQ中,匿名类型又会派上大用场!!!