规范1:
操作字符串避免性能开销
(1)确保尽量少的装箱拆箱操作 (2)避免分配额外的内存空间
string str1="str1"+9;
string str1="str1"+9.ToString();
规范4:
TryParse比Parse好
用
var sw=StopWatch.StartNew()
。。。。。
sw.stop()
测试执行时间。
规范5:
使用 int?类型来表示int和null的两种类型。
Nullable<int> i=null 等同于 int? i=null;
int? i=123;
int j;
if(i.HasValue)
{
j=i.Value;
}
规范6:
const和readonly的区别
其一const是一个编译时期的常量,readonly是一个运行期的常量。所以const效率会高些,并且 const 默认就是static不能再手动添加为static。readonyl在第一次运行时赋值然后就不能改变。特别强调的是引用本身不可以的改变但是引用类型的中的值却是可以改变的,readonly在构造器和初始化器中赋值。
其二 const只能修饰基元类型 枚举类型字符串类型 而readonly没有此限制。
规范9 运算符重载
Class Salary
{
public int RMB{get;set;}
public static Salary operator + (Salary a,Salary b)
{
b.RMB+=a.RMB;
return b
}
}
规范10 创建对象的比较器
可以实现IComparable接口
class Student:IComparable { public string Name { get; set; } public int Age { get; set; } #region IComparable Members public int CompareTo(object obj) { Student student = obj as Student; if (Age > student.Age) { return 1; } else if (Age == student.Age) { return 0; } else { return -1; } //return Age.CompareTo(student.Age); } #endregion }
OK,疑问来了。如果不想使用年龄作为比较器了,那怎么办。这个时候IComparer的作用就来了,可使用IComparer来实现一个自定义的比较器。如下:
class SortName: IComparer { #region IComparer Members public int Compare(object x, object y) { Student s1 = x as Student; Student s2 = y as Student; return s1.Name.CompareTo(s2.Name); } #endregion }
3:IComparable和IComparer的泛型实现IComparable<T>和IComparer<T>
规范12 重写Equals时也要重写GetHashCode方法
基于键值的集合(如上面的Dictionary)会根据Key值来查找Value值。CLR内部会优化这种查找,实际上,最终是根据Key值的HashCode来查找Value值。代码运行的时候,CLR首先会调用Person类型的GetHashCode,由于发现Person没有实现GetHashCode,所以CLR最终会调用Object的GetHashCode方法。将上面代码中的两行注释代码去掉,运行程序得到输出,我们会发现,Main方法和AddAPerson方法中的两个mike的HashCode是不同的。这里需要解释为什么两者实际对应调用的Object.GetHashCode会不相同。
Object为所有的CLR类型都提供了GetHashCode的默认实现。每new一个对象,CLR都会为该对象生成一个固定的整型值,该整型值在对象的生存周期内不会改变,而该对象默认的GetHashCode实现就是对该整型值求HashCode。所以,在上面代码中,两个mike对象虽然属性值都一致,但是它们默认实现的HashCode不一致,这就导致Dictionary中出现异常的行为。若要修正该问题,就必须重写GetHashCode方法。Person类的一个简单的重写可以是如下的形式:
注意 重写Equals方法的同时,也应该实现一个类型安全的接口IEquatable,所以Person类型的最终版本应该如下所示:
- public override int GetHashCode()
- {
- return this.IDCode.GetHashCode();
- }
- class Person : IEquatable<Person>
- {
- public string IDCode { get; private set; }
- public Person(string idCode)
- {
- this.IDCode = idCode;
- }
- public override bool Equals(object obj)
- {
- return IDCode == (obj as Person).IDCode;
- }
- public override int GetHashCode()
- {
- return (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.
- FullName + "#" + this.IDCode).GetHashCode();
- }
- public bool Equals(Person other)
- {
- return IDCode == other.IDCode;
- }
- }
规范13 为类型输出格式化字符串
- class Person : IFormattable
- {
- public string IDCode { get; set; }
- public string FirstName { get; set; }
- public string LastName { get; set; }
- //实现接口IFormattable的方法ToString
- public string ToString(string format, IFormatProvider formatProvider)
- {
- switch (format)
- {
- case "Ch":
- return this.ToString();
- case "Eg":
- return string.Format("{0} {1}", FirstName, LastName);
- default:
- return this.ToString();
- }
- }
- //重写Object.ToString()
- public override string ToString()
- {
- return string.Format("{0} {1}", LastName, FirstName);
- }
- }
调用者代码如下所示:
- Person person = new Person() { FirstName = "Jessica", LastName = "Hu",
- IDCode = "NB123" };
- Console.WriteLine(person);
- Console.WriteLine(person.ToString("Ch", null));
- Console.WriteLine(person.ToString("Eg", null));
输出为:
- Hu Jessica
- Hu Jessica
- Jessica Hu
- class Person
- {
- public string IDCode { get; set; }
- public string FirstName { get; set; }
- public string LastName { get; set; }
- }
针对Person的格式化器的实现为:
- class PersonFomatter : IFormatProvider, ICustomFormatter
- {
- #region IFormatProvider 成员
- public object GetFormat(Type formatType)
- {
- if (formatType == typeof(ICustomFormatter))
- return this;
- else
- return null;
- }
- #endregion
- #region ICustomFormatter 成员
- public string Format(string format, object arg,
- IFormatProvider formatProvider)
- {
- Person person = arg as Person;
- if (person == null)
- {
- return string.Empty;
- }
- switch (format)
- {
- case "Ch":
- return string.Format("{0} {1}", person.LastName,
- person.FirstName);
- case "Eg":
- return string.Format("{0} {1}", person.FirstName,
- person.LastName);
- case "ChM":
- return string.Format("{0} {1} : {2}", person.LastName,
- person.FirstName, person.IDCode);
- default:
- return string.Format("{0} {1}", person.FirstName,
- person.LastName);
- }
- }
- #endregion
- Person person = new Person() { FirstName = "Jessica", LastName = "Hu",
- IDCode = "NB123" };
- Console.WriteLine(person.ToString());
- PersonFomatter pFormatter = new PersonFomatter();
- Console.WriteLine(pFormatter.Format("Ch", person, null));
- Console.WriteLine(pFormatter.Format("Eg", person, null));
- Console.WriteLine(pFormatter.Format("ChM", person, null));
输出为:
- ConsoleApplication4.Person
- Hu Jessica
- Jessica Hu
- Hu Jessica : NB123
规范15: 使用dynamic来简化反射实
- public class DynamicSample
- {
- public string Name { get; set; }
- public int Add(int a, int b)
- {
- return a + b;
- }
- }
我们这样使用反射,调用方代码如下所示:
- DynamicSample dynamicSample = new DynamicSample();
- var addMethod = typeof(DynamicSample).GetMethod("Add");
- int re = (int)addMethod.Invoke(dynamicSample, new object[] { 1, 2 });
在使用dynamic后,代码看上去更简洁了,并且在可控的范围内减少了一次拆箱的机会,代码如下所示:
- dynamic dynamicSample2 = new DynamicSample();
- int re2 = dynamicSample2.Add(1, 2);
我们可能会对这样的简化不以为然,毕竟代码看起来并没有减少多少,但是,如果考虑到效率兼优美两个特性,那么dynamic的优势就显现出来了。如果对上面的代码执行1000000次,如下所示:
- int times = 1000000;
- DynamicSample reflectSample = new DynamicSample();
- var addMethod = typeof(DynamicSample).GetMethod("Add");
- Stopwatch watch1 = Stopwatch.StartNew();
- for (var i = 0; i < times; i++)
- {
- addMethod.Invoke(reflectSample, new object[] { 1, 2 });
- }
- Console.WriteLine(string.Format("反射耗时:{0} 毫秒",
- watch1.ElapsedMilliseconds));
- dynamic dynamicSample = new DynamicSample();
- Stopwatch watch2 = Stopwatch.StartNew();
- for (int i = 0; i < times; i++)
- {
- dynamicSample.Add(1, 2);
- }
- Console.WriteLine(string.Format("dynamic耗时:{0} 毫秒",
- watch2.ElapsedMilliseconds));
输出为:
- 反射耗时:2575 毫秒
- dynamic耗时:76 毫秒
规范17 尽量用foreach
除了能使代码简化外,还有自动能将循环代码 放入try catch中,还有就是如果该类实现了dispose接口能在结束循环的时候自动调用
规范18 foreach代替不了for
foreach循环使用了迭代器进行集合的遍历,它在FCL提供的跌代替内部维护了一个对集合版本的控制。那么什么是集合版本?简单来说,其实它就是一个整形的变量,任何对集合的增删操作都会使版本号加1.foreach会调用MoveNext方法来遍历元素,在MoveNext方法内部会进行版本号的检测,一旦检测到版本号有变动,就会抛出InvalidOperationException异常。
如果使用for循环就不会带来这样的问题。for直接使用索引器,它不对集合版本号进行判断,所以不会存在以为集合的变动而带来的异常(当然,超出索引长度这种异常情况除外)。
由于for循环和foreach循环实现上有所不同(前者索引器,后者迭代器),
规范20使用泛型代替非泛型
建议22:确保集合的线程安全
ArrayList操作的大部分应用场景不涉及多线程同步,所以它的方法更多的是单线程应用场景。线程同步是一个非常耗时(低效)的操作。若ArrayList的所有非静态方法都要考虑线程安全,那么ArrayList完全可以将这个SyncRoot变成静态私有的。现在它将SyncRoot变为公开的
集合线程安全是指多个线程上添加或删除元素时,线程键必须保持同步。
下面代码模拟了一个线程在迭代过程中,另一个线程对元素进行了删除。
class Program { static List<Person> list = new List<Person>() { new Person() { Name = "Rose", Age = 19 }, new Person() { Name = "Steve", Age = 45 }, new Person() { Name = "Jessica", Age = 20 }, }; static AutoResetEvent autoSet = new AutoResetEvent(false); static void Main(string[] args) { Thread t1 = new Thread(() => { //确保等待t2开始之后才运行下面的代码 autoSet.WaitOne(); foreach (var item in list) { Console.WriteLine("t1:" + item.Name); Thread.Sleep(1000); } }); t1.Start(); Thread t2 = new Thread(() => { //通知t1可以执行代码 autoSet.Set(); //沉睡1秒是为了确保删除操作在t1的迭代过程中 Thread.Sleep(1000); list.RemoveAt(2); }); t2.Start(); } } class Person { public string Name { get; set; } public int Age { get; set; } }
规范 30 使用linq 取代集合中的比较器
针对LINQ设计的扩展方法大多应用了泛型委托。System命名空间定义了泛型委托Action、Func和Predicate。Action用于执行一个操作,所以它没有返回值;Func用于执行一个操作并返回一个值;Predicate用于定义一组条件并判读参数是否符合条件。Select扩展方法接受的就是一个Func委托,而Lambda表达式就是一个简洁的委托,运算符“=>”左边代表的是方法的参数,右边的是方法体。
建议31:在LINQ查询中避免不必要的迭代
针对上述集合,返回年龄等于20的第一个元素。下面有两个查询模式,我们来考虑哪一个效率更高。
与First方法类似的还有Take方法,Take方法接收一个整型参数,然后我们返回该参数指定的个数。与First一样,它在满足条件后,会从当前的迭代过程中直接返回,而不是等待整个迭代过程完毕再返回。如果一个集合包含了很多的元素,那么这种查询会为我们带来可观的时间效率。//第一种 var temp = (from c in list where c.Age == 20 select c).ToList(); //第二种 var temp2 = (from c in list where c.Age >= 20 select c).First();
建议33:避免在泛型类型中声明静态成员
class MyList<T> { public static int Count { get; set; } public MyList() { Count++; } } static void Main(string[] args) { MyList<int> list1 = new MyList<int>(); MyList<int> list2 = new MyList<int>(); MyList<string> list3=new MyList<string>(); Console.WriteLine(MyList<int>.Count); Console.WriteLine(MyList<string>.Count); }若T所指定的数据类型一致,那么两个泛型对象之间还是可以共享静态成员的,如上文中的list1和list2。但是为了避免因此引起的混淆,仍旧建议在实际编码过程中,尽量避免声明泛型类型的静态成员。
非泛型类型中静态泛型方法看起来很接近该例子,但是,非泛型中的泛型方法并不会在运行时的本地代码中生成不同的类型。
规范35 使用default 为泛型类型设置默认值
建议3
建议3: 区别对待强制转型与as和is
在阐述本建议之前,首先需要明确什么是强制转型,以及强制转型意味着什么。从语法结构上来看,类似下面的代码就是强制转型。
- secondType = (SecondType)firstType;
但是,强制转型可能意味着两件不同的事情:
1)FirstType和SecondType彼此依靠转换操作符来完成两个类型之间的转型。
2)FirstType是SecondType的基类。
类型之间如果存在强制转型,那么它们之间的关系,要么是第一种,要么是第二种,不能同时既是继承的关系,又提供了转型符。
首先看第一种情况,当FirstType和SecondType存在转换操作符时的代码如下:
- class FirstType
- {
- public string Name { get; set; }
- }
- class SecondType
- {
- public string Name { get; set; }
- public static explicit operator SecondType(FirstType firstType)
- {
- SecondType secondType = new SecondType() { Name = "转型自:" + firstType.Name };
- return secondType;
- }
- }
在这种情况下,如果想转型成功则必须使用强制转型,而不是使用as操作符。
- FirstType firstType = new FirstType() { Name = "First Type" };
- SecondType secondType = (SecondType)firstType; //转型成功
- //secondType = firstType as SecondType; //编译
- //期转型失败,编译通不过
不过,这里需要讨论的不是像以上代码这样的简单应用,而是稍微复杂一点的应用。为了满足更进一步的需求,我们需要写一个通用的方法,需要对FirstType或者SecondType做一些处理,方法看起来应该像下面这样:
- static void DoWithSomeType(object obj)
- {
- SecondType secondType = (SecondType)obj;
- }
注意 是否对这种方法声明方式有一点熟悉?事实上,如果再加一个参数EventArgs,上面的方法就可以注册成为一个典型的CLR事件方法了。
如果运行本段代码,会带来一个问题:若在调用方法的时候,传入的参数是一个FirstType对象,那就会引发异常。你可能会问,在上一段代码中,有这样的写法:
- FirstType firstType = new FirstType() { Name = "First Type" };
- SecondType secondType = (SecondType)firstType;
而DoWithSomeType方法提供的代码,看起来无非像下面这样:
- FirstType firstType = new FirstType() { Name = "First Type" };
- object obj = firstType;
- SecondType secondType = (SecondType) obj;
也就是说,这段代码与上段代码相比,仅仅多了一层转型,实际上obj还是firstType,为什么转型就失败了呢?这是因为编译器还不够聪明,或者说我们欺骗了编译器。针对(SecondType) obj,编译器首先判断的是:SecondType和object之间有没有继承关系。因为在C#中,所有的类型都是继承自object的,所以上面的代码编译起来肯定没有问题。但是编译器会自动产生代码来检查obj在运行时是不是SecondType,这样就绕过了转换操作符,所以会转换失败。因此,这里的建议是:
如果类型之间都上溯到了某个共同的基类,那么根据此基类进行的转型(即基类转型为子类本身)应该使用as。子类与子类之间的转型,则应该提供转换操作符,以便进行强制转型。
注意 再次强调,转型操作符实际上就是一个方法,类型的转换需要手工写代码完成。
为了编写更健壮的DoWithSomeType方法,应该按如下方式改造它:
- static void DoWithSomeType(object obj)
- {
- SecondType secondType = obj as SecondType;
- if (secondType != null)
- {
- // 省略
- }
- }
as操作符永远不会抛出异常,如果类型不匹配(被转换对象的运行时类型既不是所转换的目标类型,也不是其派生类型),或者转型的源对象为null,那么转型之后的值也为null。改造前的DoWithSomeType方法会因为引发异常带来效率问题,而使用as后,就可以完美地避免这种问题。
现在,再来看第二种情况,即FirstType是SecondType的基类。在这种情况下,既可以使用强制转型,也可以使用as操作符,代码如下所示:
- class Program
- {
- static void Main(string[] args)
- {
- SecondType secondType = new SecondType() { Name = "Second Type" };
- FirstType firstType1 = (FirstType)secondType;
- FirstType firstType2 = secondType as FirstType;
- }
- }
- class FirstType
- {
- public string Name { get; set; }
- }
- class SecondType : FirstType
- {
- }
但是,即使可以使用强制转型,从效率的角度来看,也建议大家使用as操作符。
知道了强制转型和as之间的区别,我们再来看一下is操作符。DoWithSomeType的另一个版本,可以这样来实现,代码如下所示:
- static void DoWithSomeType(object obj)
- {
- if (obj is SecondType)
- {
- SecondType secondType = obj as SecondType;
- //省略
- }
- }
这个版本显然没有上一个版本的效率高,因为当前这个版本进行了两次类型检测。但是,as操作符有一个问题,即它不能操作基元类型。如果涉及基元类型的算法,就需要通过is转型前的类型来进行判断,以避免转型失败。
善C#程序的建议53:引用类型赋值为null与加速垃圾回收
在标准的Dispose模式中(见前一篇博客“C#中标准Dispose模式的实现”),提到了需要及时释放资源,却并没有进一步细说让引用等于null是否有必要。
有一些人认为等于null可以帮助垃圾回收机制早点发现并标识对象是垃圾。其他人则认为这没有任何帮助。是否赋值为null的问题首先在方法的内部被人提起。现在,为了更好的阐述提出的问题,我们来撰写一个Winform窗体应用程序。如下:
private void button1_Click( object sender, EventArgs e)
{
Method1();
Method2();
}
private void button2_Click( object sender, EventArgs e)
{
GC.Collect();
}
private void Method1()
{
SimpleClass s = new SimpleClass( " method1 " );
s = null ;
//其它无关工作代码(这条注释源于回应回复的朋友的质疑)
}
private void Method2()
{
SimpleClass s = new SimpleClass( " method2 " );
}
}
class SimpleClass
{
string m_text;
public SimpleClass( string text)
{
m_text = text;
}
~ SimpleClass()
{
MessageBox.Show( string .Format( " SimpleClass Disposed, tag:{0} " , m_text));
}
}先点击按钮1,再点击按钮2释放,我们会发现:
q 方法Method2中的对象先被释放,虽然它在Method1之后被调用;
q 方法Method2中的对象先被释放,虽然它不像Method1那样为对象引用赋值为null;
在CLR托管应用程序中,存在一个“根”的概念,类型的静态字段、方法参数以及局部变量都可以作为“根”存在(值类型不能作为“根”,只有引用类型的指针才能作为“根”)。
上面的两个方法中各自的局部变量,在代码运行过程中会在内存中各自创建一个“根”.在一次垃圾回收中,垃圾回收器会沿着线程栈上行检查“根”。检查到方法内的“根”时,如果发现没有任何一个地方引用了局部变量,则不管是否为变量赋值为null,都意味着该“根”已经被停止掉。然后垃圾回收器发现该根的引用为空,同时标记该根可被释放,这也表示着Simple类型对象所占用的内存空间可被释放。所以,在上面的这个例子中,为s指定为null丝毫没有意义(方法的参数变量也是这种情况)。
更进一步的事实是,JIT编译器是一个经过优化的编译器,无论我们是否在方法内部为局部变量赋值为null,该语句都会被忽略掉:
s =
null
;
在我们将项目设置为Release模式下,上面的这行代码将根本不会被编译进运行时内。
正式由于上面这样的分析,很多人认为为对象赋值为null完全没有必要。但是,在另外一种情况下,却要注意及时为变量赋值为null。那就是类型的静态字段。为类型对象赋值为null,并不意味着同时为类型的静态字段赋值为null:
private void button1_Click( object sender, EventArgs e)
{
SimpleClass s = new SimpleClass( " test " );
}
private void button2_Click( object sender, EventArgs e)
{
GC.Collect();
}
}
class SimpleClass
{
static AnotherSimpleClass asc = new AnotherSimpleClass();
string m_text;
public SimpleClass( string text)
{
m_text = text;
}
~ SimpleClass()
{
// asc = null;
MessageBox.Show( string .Format( " SimpleClass Disposed, tag:{0} " , m_text));
}
}
class AnotherSimpleClass
{
~ AnotherSimpleClass()
{
MessageBox.Show( " AnotherSimpleClass Disposed " );
}
}以上代码运行的结果使我们发现,当执行垃圾回收,当类型SampleClass对象被回收的时候,类型的静态字段asc并没有被回收。
必须要将SimpleClass的终结器中注释的那条代码启用。
字段asc才能被正确释放(注意,要点击两次释放按钮。这是因为一次垃圾回收会仅仅首先执行终结器)。之所以静态字段不被释放(同时赋值为null语句也不会像局部变量那样被运行时编译器优化掉),是因为类型的静态字段一旦被创建,该“根”就一直存在。所以垃圾回收器始终不会认为它是一个垃圾。非静态字段不存在这个问题。将asc改为非静态,再次运行上面的代码,会发现asc随着类型的释放而被释放。
上文代码的例子中,让asc=null是在终结器中完成的,实际工作中,一旦我们感觉到自己的静态引用类型参数占用内存空间比较大,并且使用完毕后不再使用,则可以立刻将其赋值为null。这也许并不必要,但这绝对是一个好习惯。试想一下在一个大系统中,那些时不时在类型中出现的静态变量吧,它们就那样静静地呆在内存里,一旦被创建,就永远不离开,越来越多,越来越多……。
建议54 标注序列化不可序列化
[Serializable]
public Class Student{
[NonSerialized]
public int age;
public string name;
[NonSerialized]
private string code;
public string Code{
get{return code;}
set{code=value;}
}
[field:NonSerialized]
public event eventhandler namechanged;
}
OnSerializing 序列化之前
[OnSerializing] private void OnSerializing(StreamingContext context) { } //格式化器在序列化开始之前调用此方法。(2) OnSerialized 序列化之后
[OnSerialized] private void OnSerialized(StreamingContext context) { } //格式化器在序列化后调用此方法。(3) OnDeserializing 反序列化之前
[OnDeserializing] private void OnDeserializing(StreamingContext context) { } //格式化器在反序列化开始之前调用此方法。(4) OnDeserialized 反序列化之后
建议56用继承Iserializable 接口更灵活地控制序列化过程[OnDeserialized] private void OnDeserialized(StreamingContext context) { }如果继承了该接口 那么会忽略掉所有的序列化特性,转而调用类型的GetObjectData()方法来构造一个serializationInfo对象,方法内部负责向这个对象添加所有序列化字段
class Program { static void Main() { Person liming = new Person() { FirstName = "Ming", LastName = "Li" }; BinarySerializer.SerializeToFile(liming, @"c:\", "person.txt"); Person p = BinarySerializer.DeserializeFromFile<Person>(@"c:\person.txt"); Console.WriteLine(p.FirstName); Console.WriteLine(p.LastName); Console.WriteLine(p.ChineseName); } } [Serializable] public class Person : ISerializable { public string FirstName; public string LastName; public string ChineseName; public Person() { } protected Person(SerializationInfo info, StreamingContext context) { FirstName = info.GetString("FirstName"); LastName = info.GetString("LastName"); ChineseName = string.Format("{0} {1}", LastName, FirstName); } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("FirstName", FirstName); info.AddValue("LastName", LastName); } }
class Program { static void Main() { Person liming = new Person() { FirstName = "Ming", LastName = "Li" }; BinarySerializer.SerializeToFile(liming, @"c:\", "person.txt"); PersonAnother p = BinarySerializer.DeserializeFromFile<PersonAnother>(@"c:\person.txt"); Console.WriteLine(p.Name); } } [Serializable] class PersonAnother : ISerializable { public string Name { get; set; } protected PersonAnother(SerializationInfo info, StreamingContext context) { Name = info.GetString("Name"); } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { } } [Serializable] public class Person : ISerializable { public string FirstName; public string LastName; public string ChineseName; public Person() { } protected Person(SerializationInfo info, StreamingContext context) { } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { info.SetType(typeof(PersonAnother)); info.AddValue("Name", string.Format("{0} {1}", LastName, FirstName)); } }在Person类型的GetObjectData方法中,有句代码非常重要:
info.SetType(typeof(PersonAnother));
它负责告诉序列化器:我要被反序列化为PersonAnother。而类型PersonAnother则很简单,它甚至都不需要知道谁会被反序列化成它,它不需要做任何特殊处理。
ISerializable接口这个特性很重要,如果运用得当,在版本升级中,它能处理类型因为字段变化而带来的问题。
建议57:实现ISerializable的子类型应负责父类的序列化
[Serializable] public class Employee : Person, ISerializable { public int Salary { get; set; } public Employee() { } protected Employee(SerializationInfo info, StreamingContext context) { Name = info.GetString("Name"); Salary = info.GetInt32("Salary"); } public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Name", Name); info.AddValue("Salary", Salary); } }
上面的例子中Person类未被设置成支持序列化。现在,假设Person类已经实现了ISerializable接口,那么这个问题处理起来会相对容易,在子类Employee中,我们只需要调用父类受保护的构造方法和GetObjectData方法就可以了。如下所示:
[Serializable] public class Person : ISerializable { public string Name { get; set; } public Person() { } protected Person(SerializationInfo info, StreamingContext context) { Name = info.GetString("Name"); } public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Name", Name); } } [Serializable] public class Employee : Person, ISerializable { public int Salary { get; set; } public Employee() { } protected Employee(SerializationInfo info, StreamingContext context) : base(info, context) { Salary = info.GetInt32("Salary"); } public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue("Salary", Salary); } }
建议72:建议75 警惕线程不会立即启动以上代码的可能输出为:
- static int _id = 0;
- static void Main()
- {
- for (int i = 0; i < 10; i++, _id++)
- {
- Thread t = new Thread(() =>
- {
- Console.WriteLine(string.Format("{0}:{1}",
- Thread.CurrentThread.Name, _id));
- });
- t.Name = string.Format("Thread{0}", i);
- t.IsBackground = true;
- t.Start();
- }
- Console.ReadLine();
- }
- Thread0:2
- Thread4:5
- Thread2:3
- Thread1:3
- Thread5:5
- Thread6:6
- Thread7:7
- Thread8:9
- Thread3:3
- Thread9:10
这段代码的输出从两个方面印证了线程不是立即启动的。
要让需求得到正确的编码,需要把上面的for循环修改成为一段同步代码:
- static int _id = 0;
- static void Main()
- {
- for (int i = 0; i < 10; i++, _id++)
- {
- NewMethod1(i, _id);
- }
- Console.ReadLine();
- }
- private static void NewMethod1(int i, int realTimeID)
- {
- Thread t = new Thread(() =>
- {
- Console.WriteLine(string.Format("{0}:{1}",
- Thread.CurrentThread.Name, realTimeID));
- });
- t.Name = string.Format("Thread{0}", i);
- t.IsBackground = true;
- t.Start();
- }
- }
建议79使用ThreadPool或BackgroundWorker代替Thread
- ThreadPool.QueueUserWorkItem((objState) =>
- {
- //工作代码
- }, null);
class Program { static void Main() { Person liming = new Person() { FirstName = "Ming", LastName = "Li" }; BinarySerializer.SerializeToFile(liming, @"c:\", "person.txt"); Person p = BinarySerializer.DeserializeFromFile<Person>(@"c:\person.txt"); Console.WriteLine(p.FirstName); Console.WriteLine(p.LastName); Console.WriteLine(p.ChineseName); } } [Serializable] public class Person : ISerializable { public string FirstName; public string LastName; public string ChineseName; public Person() { } protected Person(SerializationInfo info, StreamingContext context) { FirstName = info.GetString("FirstName"); LastName = info.GetString("LastName"); ChineseName = string.Format("{0} {1}", LastName, FirstName); } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("FirstName", FirstName); info.AddValue("LastName", LastName); } }