【备忘】C#语言基础-2
泛型
CollectionClass<ItemClass> items = new CollectionClass<ItemClass>();
items.Add(new ItemClass());
泛型并不限于类,还可以创建泛型接口、泛型方法(可以在非泛型类上定义),甚至泛型委托。这将极大地提高代码的灵活性,正确使用泛型可以显著缩短开发时间。
可空类型
有时让值类型为空是很有用的(尤其是处理数据库时), 泛型使用System.Nullable
System.Nullable<int> nullableInt;
nullableInt = null;
if (nullableInt == null)
{
...
}
if (nullableInt.HasValue)
{
...
}
这不适用于引用类型,即使引用类型有一个HasValue 属性,也不能使用这种方法,因为引用类型的变量值为 null,就表示不存在对象,当然就不能通过对象来访问这个属性,否则会抛出一个异常。
可空类型非常有用,以致于修改了C#语法。声明可空类型的变量不使用上述语法,而是使用下面的语法:
int? nullableInt;
int?是System.Nullable
的缩写,但更便于读取。
??运算符
下面两个表达式是的作用是等价的:
op1 ?? op2
op1 == null ? op2 : op1
常用泛型类型
类 型 | 说 明 |
---|---|
List |
T 类型对象的集合 |
Dictionary<K, V> | 与K 类型的键值相关的V 类型的项的集合 |
List
List<T> myCollection = new List<T>();
成员 | 说明 |
---|---|
int Count | 该属性给出集合中项的个数 |
void Add(T item) | 把一个项添加到集合中 |
void AddRange(IEnumerable |
把多个项添加到集合中 |
IList |
给集合返回一个只读接口 |
int Capacity | 获取或设置集合可以包含的项数 |
void Clear() | 删除集合中的所有项 |
bool Contains(T item) | 确定item是否包含在集合中 |
void CopyTo(T[] array, int index) | 把集合中的项复制到数组array中,从数组的索引index 开始IEnumerator |
int IndexOf(T item) | 获取item的索引,如果集合中并未包含该项,就返回−1 |
void Insert(int index, T item) | 把item插入到集合的指定索引位置上 |
bool Remove(T item) | 从集合中删除第一个item,并返回true;如果item不包含在集合中,就返回false |
void RemoveAt(int index) | 从集合中删除索引index 处的项 |
List
T itemAtIndex2 = myCollectionOfT[2];
排序
要针对List
一般情况下,给列表排序需要有一个方法来比较两个T 类型的对象。要在列表中搜索,也需要一个方法来检查T 类型的对象,看看它是否满足某个条件。定义这样的方法很简单,这里给出两个可以使用的泛型委托类型:
- Comparison
:这个委托类型用于排序方法,其返回类型和参数如下:
int method(T objectA, T objectB)
- Predicate
:这个委托类型用于搜索方法,其返回类型和参数如下:
bool method(T targetObject)
Dictionary<K, V>
这个类型可以定义键/值对的集合。与本章前面介绍的其他泛型集合类型不同,这个类需要实例化两个类型,分别用于键和值,以表示集合中的各个项。
事件
订阅一个事件的含义是提供代码,在事件发生时执行这些代码,它们称为事件处理程序。
单个事件可供多个处理程序订阅,在该事件发生时,这些处理程序都会被调用,其中包括引发该事件的对象所在的类中的事件处理程序,但事件处理程序也可能在其他类中。
事件处理程序本身都是简单的方法。对事件处理方法的唯一限制是它必须匹配于事件所要求的返回类型和参数。这个限制是事件定义的一部分,由一个委托指定。
定义事件
在定义事件前,必须先定义一个委托类型,以用于该事件,这个委托类型指定了事件处理方法必须拥有的返回类型和参数。
public delegate void MessageHandler(string messageText);
定义了委托(或者找到合适的现有委托)后,就可以定义事件。
public event MessageHandler MessageArrived;
以这种方式声明了事件后,就可以引发它,方法是按名称来调用它,就好像它是一个其返回类型和参数是由委托指定的方法一样。
MessageArrived("This is a message.");
如果定义该委托时不包含任何参数,就可以使用下面的代码:
MessageArrived();
订阅事件的类是Display,它包含一个方法DisplayMessage(),其定义如下所示:
public class Display
{
public void DisplayMessage(string message)
{
Console.WriteLine("Message arrived: {0}", message);
}
}
这个方法匹配于委托类型(而且是公共的, 如果类不是生成该事件的类,则其事件处理程序就必须是公共的),所以可以用它来响应MessageArrived 事件。
把它们关联起来,开始执行任务。
MessageArrived += new MessageHandler(myDisplay.DisplayMessage);
多用途事件处理程序
Timer.Elapsed 事件的委托包含了事件处理程序中常见的两类参数,如下所示:
-
object source——引发事件的对象的引用
-
ElapsedEventArgs e——由事件传送的参数
在这个事件(以及许多其他的事件)中使用 object 类型参数的原因是,我们常常要为由不同对象引发的几个相同事件使用同一个事件处理程序,但仍要指定哪个对象生成了事件。
EventHandler和泛型EventHandler
类型
匿名方法
初始化器
当构造函数不能完全初始化一些参数和属性时,可以使用初始化器(格式如下):
<ClassName> <variableName> = new <ClassName>
{
<propertyOrField1> = <value1>,
<propertyOrField2> = <value2>,
...
<propertyOrFieldN> = <valueN>
};
集合初始化器
List<Curry> curries = new List<Curry>();
curries.Add(new Curry("Chicken", "Pathia", 6));
curries.Add(new Curry("Vegetable", "Korma", 3));
curries.Add(new Curry("Prawn", "Vindaloo", 9));
可以用如下代码替换:
List<Curry> moreCurries = new List<Curry>
{
new Curry
{
MainIngredient = "Chicken",
Style = "Pathia",
Spiciness = 6
},
new Curry
{
MainIngredient = "Vegetable",
Style = "Korma",
Spiciness = 3
},
new Curry
{
MainIngredient = "Prawn",
Style = "Vindaloo",
Spiciness = 9
}
};
类型推理
关键字var
var <varName> = <value>;
在这行代码中,变量
隐式地类型化为 value 的类型。注意,类型的名称并不是 var。 依赖编译器去确定变量的类型。
注意:如果编译器不能确定用 var 声明的变量类型,代码就不会编译。因此,在用 var 声明变量时,必须同时初始化该变量,因为如果没有初始值,编译器就不能确定变量的类型。
动态查找
C#处理另一种语言创建的对象时,显得很笨拙。例如,假定代码从JavaScript 中获得了一个带Add()方法的对象,该方法把两个数字加在一起。如果没有动态查找功能,调用这个方法的代码就如下所示:
ScriptObject jsObj = SomeMethodThatGetsTheObject();
int sum = Convert.ToInt32(jsObj.Invoke("Add", 2, 3));
ScriptObject 类型(这里不深入探讨)提供了一种访问 JavaScript 对象的方式,但不能执行如下操作:
int sum = jsObj.Add(2, 3);
动态查找功能,它允许编写上述代码。
动态类型
C# 4 引入了dynamic 关键字,以用于定义变量。例如:
dynamic myDynamicVar;
与前面介绍的 var 关键字不同,的确存在 dynamic 类型,所以在声明 myDynamicVar 时,无需初始化它的值。
dynamic 类型不同寻常之处是,它仅在编译期间存在,在运行期间它会被System.Object 类型替代。
一旦有了动态变量,就可以继续访问其成员(这里法有列出实际获取变量值的代码)。
myDynamicVar.DoSomething("With this!");
无论怎样这行代码都会编译。但是如果请求的成员不存在,执行这行代码时会生成一个异常。
高级参数方法
可选参数
C++中的默认参数值。
参数命名
可以指定某个可选参数赋值,通过参数名。
拓展方法
要求:
-
方法必须是静态的。
-
包含它的类也必须是静态的。
-
方法必须包含一个参数,表示调用扩展方法的类型实例(这个参数在这里称为实例参数)。
-
实例参数必须是为方法定义的第一个参数。
-
除了this 关键字之外,实例参数不能有其他修饰符。
public static class ExtensionClass
{
public static <ReturnType> <ExtensionMethodName>(
this <TypeToExtend> instance)
{
...
}
}
导入了包含静态类(其中包括此方法)的名称空间后(也就是使扩展方法变得可用), 就可以编写如下代码:
<TypeToExtend> myVar;
// myVar is initialized by code not shown here.
myVar.<ExtensionMethodName>();
这个调用实际上与下面的调用相同,但语法更筒单:
<TypeToExtend> myVar;
// myVar is initialized by code not shown here.
ExtensionClass.<ExtensionMethodName>(myVar);
Lambda 表达式
用delegate 关键字定义内联的匿名方法:
myTimer.Elapsed +=
delegate(object source, ElapsedEventArgs e)
{
Console.WriteLine(
"Event handler called after {0} milliseconds.",
(source as Timer).Interval);
};
运用 Lambda 表达式来写这段代码:
myTimer.Elapsed += (source, e) => Console.WriteLine(
"Event handler called after {0} milliseconds.",
(source as Timer).Interval);
Lambda 表达式由3 个部分组成:
-
放在括号中的参数列表(未类型化)
-
=>运算符
-
C#语句
多行代码用花括号括起来,有返回值需要 return 语句,不像单行代码(一个表达式)编译器会自己作出判断,不用写出 return 。
Lambda 表达式用作委托
一般可以把拥有至多8 个参数的Lambda 表达式表示为如下泛型类型, 它们都在System 命名空间中定义:
-
Action 表示的Lambda 表达式不带参数,返回类型是void
-
Action<>表示的Lambda 表达式有至多8 个参数,返回类型是void
-
Func<>表示的Lambda 表达式有至多8 个参数,返回类型不是void
Action<>有至多 8 个泛型类型的参数,分别用于 Lambda 表达式的 8 个参数,Func<>有至多 9个泛型类型的参数,分别用于Lambda 表达式的8 个参数和返回类型。在Func<>中,返回类型总是在列表的最后。
下面的Lambda 表达式:
(int paramA, int paramB) => paramA + paramB
可以表示为Func<int, int, int>类型的委托,因为它有两个int 参数,返回类型是int。