9 集合与泛型
解决内存中维护和操作一组数据点的问题
解决了.net1.1中装箱和类型安全问题
-
集合类的动机
非泛型集合:通常设计为操作System.Object System.Collections
-
泛型类型的优势
a. 性能更好,因为他们不会导致装箱和拆箱的损耗
b. 更加类型安全,因为我们指定他们包含的类型
c. 大幅减少了构建自定义集合类型的需要,因为创建泛型容器时指定了 类型的类型
-
泛型类型参数的作用
读作 of T
只有类,结构,接口和委托可以使用泛型,枚举类型不可以
-
System.Collection.Generic
为大多数非泛型接口定义了泛型版本
List, Stack, Queue, SortedSet
-
System.Collection.ObjectModel
ObservableColection, 内容发生变化时通知外部对象的动态数据集合
ReadOnlyObservableColection
ObservableCollection<Person> person1 = new ObservableCollection<Person> { new Person{Name="jack",Age=10 }, new Person{Name="mary",Age=12 }, }; person1.CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) { MessageBox.Show("observablecollection changed, {0}" + person1[0].Name); }; person1[0].Name = "jack2";//useless person1.Add(new Person { Name = "jeff" });
- 创建自定义泛型方法
-
创建自定义泛型结构和类
default和泛型一起使用时,表示一个类型参数的默认值
数值默认为0,引用类型默认为null
- 类型参数的约束
! Operator ‘+’ cannot be applied to operands of type ‘T’ and ‘T’where T : struct must have System.ValueType in its chain where T : class must be a reference type where T : new() must have a default constructor. where T : NameOfBaseClass must be derived from the class specified by NameOfBaseClass. where T : NameOfInterface
10 委托,事件和Lambda表达式
委托类型用来定义和响应应用程序中的回调..net委托类型是一个类型安全的对象,指向以后可以调用的方法.允许内存中的对象进行双向对话
回调:对象与创建它的实体进行通信,低层函数执行调用高层的代码
事件, event关键字
Lambda表达式,用代码语句代替强类型委托.Lambda是匿名方法的一种伪装
-
委托类型
组成:调用的方法的名称,该方法的参数,该方法的返回值类型
.net委托可以指向静态方法,也可以指向实例方法
定义: 委托的名称可以自由选择,但是必须匹配它指向的方法的签名(参数类型和个数)
C#委托类型定义会生成一个密封类,继承自System.MulticastDelegate,默认含有3个方法
// 伪代码 public sealed class DelegateName : System.MulticastDelegate { public delegateReturnValue Invoke(allDelegateInputRefAndOutParams); public IAsyncResult BeginInvoke(allDelegateInputRefAndOutParams, AsyncCallback cb, object state); public delegateReturnValue EndInvoke(allDelegateRefAndOutParams, IAsyncResult result); }
- 简单示例
public delegate int BinaryOP(int x, int y); public class DelegateClass { public int Add(int x, int y) { return x + y; } public static int Add2(int x, int y) { return x + y; } } DelegateClass dg = new DelegateClass(); BinaryOP binaryOP = new BinaryOP(dg.Add); BinaryOP binaryOP2 = new BinaryOP(DelegateClass.Add2); Console.WriteLine("the sum is "+binaryOP.Invoke(1,2)); Console.WriteLine("the sum is " + binaryOP2(1, 2));
-
使用委托发送对象状态通知
定义发送给调用者的委托类型
私有委托类型变量,创建委托对象注册方法
创建调用委托对象的方法
//this.listOfhandlers => OnCarEngineEvent car.RegisterCarEngine(new Car.CarEngineHandler(OnCarEngineEvent)); //Accelerate => OnCarEngineEvent for (int i = 0; i < 6; i++) car.Accelerate(20);
多路广播:
委托对象维护一个可调用方法的列表
方法组转换:调用以委托作为参数的方法时直接提供方法的名称,而不用创建委托对象+=: Delegate.Combine() this.listOfhandlers += methodToCall; <=> if (listOfHandlers == null) listOfHandlers = methodToCall; else Delegate.Combine(listOfHandlers, methodToCall); -=: Delegate.Remove()
car.RegisterCarEngine(new Car.CarEngineHandler(OnCarEngineEvent)); <=> car.RegisterCarEngine(OnCarEngineEvent);
-
泛型委托
使用委托在应用程序中进行回调的步骤
自定义与要指向的的方法的格式相匹配的委托
创建自定义委托的实例,方法名作为其构造函数的参数
通过Invoke()来调用方法
Action<>, Func<>
接受一组参数并返回一个值(或void)的委托.
Action只能指向void方法
Func可以指向具有返回值的方法
//action func Action<string, string, int> action = new Action<string, string, int>(DisplayAction); action(" action", " message", 2); //func Func<string, string, string> func = new Func<string, string, string>(DisplayFunc); Console.WriteLine("func is {0}", func(" func", " message")); private static void DisplayAction(string v1, string v2, int v3) { Console.WriteLine("DisplayAction" + v1 + v2 + v3); } private static string DisplayFunc(string v1, string v2) { return "DisplayFunc" + v1 + v2; }
-
事件 event
公共的委托成员打破了封装,而且我们不希望给其他应用程序改变委托指向及未经许可直接调用成员的权力
event关键字,编译器会自动提供注册和注销方法及必要的委托类型成员变量.event关键字只是节省了打字的时间.
public delegate void CarEngineHandler(string msgForCaller); public event CarEngineHandler Exploded; public event CarEngineHandler AbouttoBlow;
自定义事件参数
低层委托的第一个参数是System.Object,表示对发送事件的对象的引用
第二个参数是System.EventArgs的子类型,表示该事件相关的信息
匿名方法: 事件注册时直接将一个委托与一段代码关联.匿名方法最后一个大括号以分号结束
匿名方法不能访问定义方法中的ref或是out参数
public delegate void CarEngineHandlerEvent(object sender, CarEventArgs e); public event CarEngineHandlerEvent ExplodedEvent; public class CarEventArgs : EventArgs { public readonly string msg; public CarEventArgs(string message) { msg = message; } } car.ExplodedEvent += delegate (object sender, CarEventArgs e) { Console.WriteLine("object is {0}, careventargs is {1}",sender,e.msg); };
-
Lambda表达式
要处理的参数 => {处理参数的语句; 语句2; 语句3 }
Lambda表达式只是用更简单的方法来写匿名方法,可应用于任何匿名方法或者强类型委托
car.ExplodedEvent += delegate (object sender, CarEventArgs e)
{
Console.WriteLine("object is {0}, careventargs is {1}", sender, e.msg);
};
car.ExplodedEvent += (sender, e) =>
{
Console.WriteLine("object is {0}, careventargs is {1}", sender, e.msg);
Console.WriteLine("object2 is {0}, car2 eventargs is {1}", sender, e.msg);
};
11 高级c#语言特性
-
索引器方法
索引器的实现基于集合类型如何实现调用者查询子项
允许我们构建以类似访问数组的语法来访问内部子类型的自定义类型
private ArrayList arPeople = new ArrayList();
public Person this[int index]
{
get { return (Person)arPeople[index]; }
set { arPeople.Insert(index, value); }
}
public IEnumerator GetEnumerator()
{
return arPeople.GetEnumerator();
}
使用字符串值索引对象,可直接获得索引器方法功能,而不用构建自定义的非泛型类来支持字符串索引.
private Dictionary<string, Person> dictionaryPerson = new Dictionary<string, Person>();
public Person this[string name]
{
get { return dictionaryPerson[name]; }
set { dictionaryPerson.Add(name, value); }
}
重载索引器方法
索引器方法支持多维
接口类型上定义索引器
索引器可以在.net接口上定义,这样实现类型就可以提供自定义实现
public interface IStringContainer
{
string this[int index] { get; set; }
}
2. 操作符重载
通常构建原子数据类型时才有用
操作符对不同类型做出不同反应, +: 对数字类型求值,对字符串类型串联
重载二元操作符 +
// operator ‘Point.operator +(Point, Point)’ must be declared static and public
public static Point operator +(Point p1, Point p2)
{
return new Point { X = p1.X + p2.X, Y = p1.Y + p2.Y };
}
简写赋值操作符会根据相关二元操作符自动具有相关功能 += / +
point1 += point2;
Console.WriteLine("point1 is {0}", point1);
can be overloaded. +, -,! , ~, ++, --, true, false
can be overloaded. +, -, *, /, %, &, |, ^, <<, >>
can be overloaded 对应的操作符要一起重载 (i.e., < and >, <= and >=, == and !=) ==,!=, <, >, <=, >=
cannot be overloaded. [] The [] operator. () The () operator
简写赋值操作符会根据相关二元操作符自动具有相关功能 `+=, -=, *=, /=, %=, &=, |=, ^=, <<=,>>= `
-
自定义类型转换
类似操作符重载,使用关键字implicit/explicit
public static implicit operator Rectangle(Square square) { return new Rectangle { Height = square.Length, Width = 2 * square.Length }; } public static explicit operator Square(Rectangle rectangle) { Square square = new Square { Length = rectangle.Width < rectangle.Height ? rectangle.Width : rectangle.Height }; return square; } Rectangle rectangle = new Rectangle(15,4); Square square = (Square)rectangle; square.Draw(); rectangle = square; rectangle.Draw();
-
拓展方法
不改变原始类型的情况下,为类或结构添加新的方法或属性
定义: 1, 拓展方法必须是静态方法并定义在静态类中
2, 用this限定表示被拓展的项,this关键字修饰且仅修饰第一个
static class MyExtension
{
public static int ReverseDigits(this int i)
{
char[] digits = i.ToString().ToCharArray();
int begin = ;
int end = digits.Length - ;
char flag;
while (begin < end)
{
flag = digits[end];
digits[end] = digits[begin];
digits[begin] = flag;
begin++;
end--;
}
return Int32.Parse(new string(digits));
}
public static string ToStringEx(this char[] digits)
{
string str = string.Empty;
if (digits == null)
return str;
foreach (char digit in digits)
str += digit;
return str;
}
}
int i = ;
int j = i.ReverseDigits();
Console.WriteLine("Extension Method int reverse is {0} ", j);
char[] charArray = new char[] {'1','2','e','5' };
charArray.ReverseDigits();
Console.WriteLine("Extension Method int reverse is {0} ", charArray.ToStringEx());
-
匿名类型
定义匿名类型 var
同样值的匿名类型,按值比较相等.按引用不一样.默认的类型一致
var car = new { Name = "car", Color = "red" }; var mycar = new { Name = "car", Color = "red" }; //same Equals //not same == //same GetType<> f__AnonymousType0`2 if (car.Equals(mycar)) Console.WriteLine("same Equals"); else Console.WriteLine("not same Equals"); if (car==mycar) Console.WriteLine("same =="); else Console.WriteLine("not same =="); if (car.GetType().Name==(mycar.GetType().Name)) Console.WriteLine("same GetType "+ car.GetType().Name); else Console.WriteLine("not same GetType");
-
指针类型
第三种数据类型.绕开CLR的内存管理.
VS: Properties=> Build => Allow Unsafe Code
unsafe { int right = 10; int* ptr = &right; *ptr = 123; Console.WriteLine(right); }
stackalloc关键字,直接从调用栈分配内存
fixed关键字,变量地址语句在执行过程中保持不变
sizeof关键字, 获取值类型的字节大小,可用在unsafe上下文中
12 LINQ to Object
.net 3.5 引入了LINQ Language-Integrated Query
LINQ操作符只是System.Linq.Enumerable类型静态方法调用的快捷形式.
-
LINQ 特有的编程结构
LINQ可以理解为直接嵌入c#语法的强类型查询语言
LINQ相关的特性
隐式类型本地变量 var
对象/集合初始化语法 new Class{}
lambda表达式 =>
拓展方法
匿名类型 var car = new {};
-
作用
LINQ的API意图提供一种统一操作各种数据类型的方式: database,xml, array, collection
- 用于原始数组
延迟执行的作用在迭代内容之前,他们不会真正的进行计算立即执行string[] currVideos = new string[] { "morrowind", "red", "gone with the wind", "fallout", "modern family" }; var subset = from g in currVideos where g.Contains(" ") orderby g select g; foreach (var v in subset) Console.WriteLine(v);
这些方法将会立即执行LINQ查询并取得一份数据快照ToArray<T>(),ToDictionary<TSource,TKey>(), and ToList<T>()
- 用于集合对象
用于非泛型集合 OfType对包含在非泛型集合里的数据授予数据结构进行迭代操作List<Car> cars = new List<Car>() { new Car { Name = "car1",maxSpeed=70}, new Car { Name = "car2",maxSpeed=130}, new Car { Name = "car3",maxSpeed=140}, new Car { Name = "car4",maxSpeed=150}}; var fastcars = from g in cars where g.maxSpeed > 100 && g.maxSpeed < 143 orderby g.Name select g; foreach (var car in fastcars) Console.WriteLine(car.Name + car.maxSpeed + ";");
OfType筛选数据ArrayList cars2 = new ArrayList() { new Car { Name = "car1",maxSpeed=70}, new Car { Name = "car2",maxSpeed=130}, new Car { Name = "car3",maxSpeed=140}, new Car { Name = "car4",maxSpeed=150}, new Car { Name = "car5",maxSpeed=110}, new Car { Name = "car6",maxSpeed=120}}; var carsEnum = cars2.OfType<Car>(); var fastcars2 = from f in carsEnum where f.maxSpeed > 100 & f.maxSpeed < 150 orderby f.maxSpeed select f; foreach (var car in fastcars2) Console.WriteLine(car.Name + " " + car.maxSpeed + ";");
ArrayList myStuff = new ArrayList(); myStuff.AddRange(new object[] { 1, 2, 4, 3, "as", "ds", 'w', 'e', new Car() }); var myInts = myStuff.OfType<int>(); foreach (int i in myInts) Console.WriteLine(" OfType " + i + ";");
-
LINQ查询操作符
from in 定义LINQ表达式的主干,允许从合适的容器中提取数据子集
where 对提取出的项的限制条件
select 从容器中选择出序列
join,on, equals, into 基于制定的键做关联操作
orderby, ascending/descending 排列子集进行升序或是降序
group by 基于特定的值进行分组
LINQ查询表达式在编译时校验,操作符的顺序很重要
from后的项用来匹配查询条件及选择出的项
in后面的项表示要查询的数据容器
where后预期接一个结果为bool值得表达式
select后投影出新的数据类型
(LINQ set)
Count() 返回数据项的总数,Average(),Max()..
orderby 排序
Except() 返回两个容器的不同之处
Intersect() 返回两个容器的共同数据项
Union() 合并容器并去除重复项
Concat() 连接两个容器
Distinct() 去除重复数据项
-
查询语句的内部表示
编译器将所有的LINQ操作符都翻译为对Enumerable类中方法的调用
var fastcars2 = from f in carsEnum where f.maxSpeed > 10 & f.maxSpeed < 150 orderby f.maxSpeed select new { f.Name, f.maxSpeed };//select f; var fastcarEnum = carsEnum.Where(f => f.maxSpeed > 10 & f.maxSpeed < 150) .OrderByDescending(a => a.maxSpeed) .Select(a => new { a.Name, a.maxSpeed });
13 对象的生命周期
CLR通过GC(garbage collection)来管理已分配的类实例(对象)
.net对象被分配到一块叫托管堆(managed heap)的内存区域上,GC会自动管理这块内存
-
类,对象和引用
类:只是一个蓝图,描述了这个类的实例在内存中看起来是什么样子
对象:类实例,分配在托管堆上.一个类可以有任意个对象
引用:new关键字返回的指向堆上对象的引用(指针)
点操作符可以调用对象的成员.
-
对象生命周期的基础
当对象从代码库的任何部分都不可访问时,GC会进行回收.GC机制是独立于代码库的
将对象设置为null也不会触发GC回收
-
应用程序根
根是一个存储位置,保存着对托管对上一个对象的引用.
GC通过根判断对象是否可被访问,进一步标记是否回收
-
对象的代
代的设计思路:对象在堆存在时间越长就越可能应该保留
第0代: 从没被标记为回收的新分配的对象
第1代: 上一次垃圾回收中被标记为回收但没有被回收的对象
第2代: 一次以上的垃圾回收后仍没有被回收的对象
-
.net 1.0 - 3.5 并发进行垃圾回收
GC会挂起所有活动进程
-
4.0以后
后台垃圾回收用于第二代,回收时间较长
专用后台线程回收暂时代(0/1),存在时间短
Console.WriteLine("GC GetTotalMemory" + GC.GetTotalMemory(false) + ";"); Console.WriteLine("GC MaxGeneration" + GC.MaxGeneration + ";"); //强制回收内存 GC.Collect(); GC.WaitForPendingFinalizers();
-
构建可终结对象
重写Finalize()方法,会在垃圾回收之前调用,System.Object中的虚方法,但可能不存在
-
构建可处置对象
实现IDidposable接口,其中定义了一个Dispose()的方法
结构和类都可以实现IDisposeable
只有类可实现Finalize()
using(){} 会自动调用Dispose()
-
延迟对象实例化
System.Lazy<>: 提供懒加载.定义的数据在代码库实际使用之前是不会被创建的.
//private AllTracks allSongs = new AllTracks();
//public AllTracks GetAllTracks()
//{ return allSongs; }
private Lazy<AllTracks> allSongs = new Lazy<AllTracks>(); public AllTracks GetAllTracks() { return allSongs.Value; }