深入理解C#:编程技巧总结(一)

原创文章,转载请注明出处! 以下总结参阅了:MSDN文档、《C#高级编程》、《C#本质论》、前辈们的博客等资料,如有不正确的地方,请帮忙及时指出!以免误导!

本文 博客园地址

1.实现多态性的两种方式:继承抽象类、实现接口

其实就是协变的应用,通过把对象向上转型为基类或接口类型,对它调用成员,可实现多态性,即运行时调用的是对应对象的实现版本成员。这两种方式的区别:
- 继承抽象类:会用掉唯一1次的继承机会,但可以继承任何成员(包括字段),自由度高
- 实现接口:必须实现所有成员,不能包含字段,但可以实现多个接口

2.不要创建可变的值类型(结构、枚举),若要改变,请用一个方法来返回一个新实例。要时刻注意频繁的装箱与拆箱对性能的影响
3.仅在能一眼看出变量的类型时,才使用var声明
4.定义值类型时,它的大小不要超过16字节,否则影响性能(频繁复制时),要么改为使用引用类型,要么让它按ref引用传递
5.值类型数组之间不能直接互相转换,可以通过一次中间转换为Array来达到目的,如:

(int[])(Array)new uint[32]
但应注意可能在不同的CLR实现中表现不同!

6.数组与List
  • 如果元素数量固定,且不涉及转型,则使用数组效率更高。
  • 在元素数量可能发生变化的情况下,就不应该使用数组,而应该使用List
  • 无论是数组还是List,元素个数也不能太多,避免成为占用内存超过85000字节的大对象,因为大对象将会被分配到单独的堆进行处理,在回收大对象时效率较低。
7.字符串操作
  • 字符串字面量、字符串常量,直接用”+”相连效率高,因为:string str = "srf"+"ttt"+"ccc";会直接编译成string str = "srftttccc";,同样适用于字符串常量。
  • 尽量避免对变量的装箱:字符串+变量,较好的做法是:字符串+变量.ToString()
  • 频繁操作字符串时用StringBuilder,并制定足够大的容量,而string.Format("{0}{1}{2}",str1,str2,str3);内部也是用StringBuilder
8.类型转换

字符串转其它基元类型:
- 默认十进制:用Parse()、TryParse(),如:int.TryParse("24");,其中TryParse效率更高
- 指定基数进制形式来解析:Convert.ToInt32("0xFF",16);
- 从字节数组中提取一段,转为基元类型:BitConvert.ToInt32(Byte[] arr, int startIndex);

自定义类型之间的强制转换:
从基类强制转换为子类时,安全的做法是使用”as”,若目标为null或类型不兼容转换失败,均会返回null,而不会引发错误,如基类Person,它的子类Man、Women

Person person = new Man();//自动向基类隐式转换,但person的运行时类型仍为Man
Women women = (Women)person; //错误
Women women = person as Women; //women为null ,因为男人不能转换为女人

但需注意”as”只能应用于引用类型或可为null类型。若目标可能为基元类型,则应该通过”is”操作符来过滤

if(!(person is int))
{
    Women women = person as Women;
}

子类与子类之间的横向转换,应该定义转换操作符(关键字implicit、explicit)

9.获取一个可空类型Nullable的值,安全简单的做法是用”??”,如 int j = i ?? 0;,普通做法:

if(i.HasValue()) { int j = i.Value; }

10.常量const和只读字段readonly的区别:
  • const是编译期常量,它总是静态的,编译时直接用实际值填充。而readonly是一个运行时常量。
  • const只能修饰基元类型、枚举类型、字符串类型,而readonly没有限制。
  • const一经声明就必须初始化,且之后就无法再改变。而readonly可显式初始化,也可不初始化,它的值可以通过构造函数来改变(即每个实例有自己的readonly只读字段值)
    注意:除了构造函数之外,都无法改变readonly的值,对于引用类型是无法改变它的引用,即它只能引用同一对象。但该对象本身是可以被修改的。
11.枚举类型
  • 枚举类型可以为从byte到ulong的基元类型,定义枚举时应该始终为它定义一个零值,因为声明一个枚举变量而未初始化时的默认值将是0
  • 除了0值,要么都不为成员显式赋值,要么就全部赋值(如应用了Flags特性的标志枚举),否则未赋值的成员将等于它前一个成员的值加1,因为枚举成员的值默认是按顺序逐个加1
  • 对枚举应用[Flags]特性,可以定义一个标志枚举,它的成员值通常初始化为2的次幂,之后就可以通过按位运算来判断、合并枚举成员了。
  • 定义一个枚举来专门负责表示状态的信息,这样使代码更易理解。如用枚举成员on、off来代替true、false或0、1
12.如果需要,应该为类型重载常用的运算符和比较运算符,如重载”>”以实现person1>person2
13.若该类型有泛型版本,则应该使用泛型版本,因为泛型类型效率更高(避免了装箱、拆箱、类型转换)
14.相等性
  • 值类型:对于值相等的两个值类型变量A、B,”A==B”和”A.Equals(B)”都返回true,而Object.ReferenceEquals(A,B)总是返回false。
  • 引用类型:Object.ReferenceEquals(A,B)比较的是引用是否相等,而默认的A.Equals(B)也是比较的引用,需要重载Equals()方法来实现引用类型之间的”值相等性比较”(如:当person1.ID == person2.ID时,person1.Equals(person2)返回true,来表示他们相等)
  • 注意1:重写了Equals()方法,最好也一起重写GetHashCode()方法,因为对于不同的对象,默认的GetHashCode()返回的值将永远不同,而若把对象作为Dictionary
15.ToString()方法

应该总是为自定义类型重写Object的ToString()方法,最好还要实现IFormattable接口,该接口的ToString(string format, IFormatProvider formatProvider)提供了根据参数来输出特定的格式化形式。如:

public string ToString(string format, IFormatProvider formatProvider)
{
    switch(format)
    {
        case "CH":
                return this.ToString();
        case "EN":
                return string.Format("{0}{1}",FirstName,LastName);
        ......
    }
}
//调用
Console.WriteLine(person.ToString("EN",null));
16.对象的浅拷贝与深拷贝
  • 浅拷贝:使用Object基类的实例方法MemberwiseClone()来获得对象的一个浅拷贝副本。
  • 深拷贝:通过系列化与反系列化来深拷贝一个对象。
    通常做法,如下:接口ICloneable唯一成员是object Clone(),实现该接口只是为了表明该类型的实现可以被拷贝
[Serializable]
class Person : ICloneable
{
    public string ID {get;set;}
    public int Age {get;set;}
    public Work work {get;set;} 
    //实现ICloneable接口的Clone()
    public object Clone()
    {
        return this.MemberwiseClone();
    }
    //自定义深拷贝方法
    public Person DeepClone()
    {
        using (Stream objectStream = new MemoryStream())
        {
            IFormatter formatter = new BinaryFormatter();
            formatter.Serialize(objectStream, this);
            objectStream.Seek(0, SeekOrigin.Begin);
            return formatter.Deserialize(objectStream) as Person;
        }
     }
}
17.集合的遍历
  • for循环:采用索引器,for循环的优点是遍历过程中可以修改集合的元素。
  • foreach循环:采用迭代器,遍历过程中无法对集合增删元素操作,因为迭代器只对原始版本的集合进行遍历,每次迭代都会进行版本判断,若集合发生变化,将抛出异常。- - - - foreach循环的优点是语法更简洁,且迭代完毕后自动调用Dispose()(foreach循环内部使用了try…finally)
18.选择正确的集合:详解请参见《C#高级编程》,书中对集合讲的很细
  • 线性:集合的每个元素都是是1对1的,大部分常用集合都是线性集合
  • 非线性:1对多、多对1、多对多(树、集HashSet、图)
  • 直接存取:具有索引器,元素按索引器排列,访问、查找速度快,在末尾添加删除速度也快,但在中间删除、插入元素效率低(需要移动后面的所有元素)。(数组、List、字符串、结构)
  • 顺序存取:即线性表,可动态扩大或缩小,通过对地址的引用来搜索元素,删除、插入元素效率高,但查找效率低(需要遍历查找)(Stack、Queue、Dictionary
19.泛型
  • 避免为自定义泛型定义静态成员,在不同的类型之间共享静态成员没意义。
  • 记得为泛型参数设定必要的约束,因为约束之后可以使泛型参数成为一个实实在在的”对象”,可以访问到约束类型的实例成员,而不做约束的话仅仅是一个object对象
  • 必要时用default(T)为泛型类型变量指定默认值,如T param = default(T);
20.委托

预定义的委托类型能满足大部分日常需求,我们没有必要声明自己的委托类型。
- Action,Action

public delegate void EventHandler(object sender, EventArgs e);
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
  • 线程中的委托:
public delegate void ThreadStart(); //无参数
public delegate void ParameterrizedThreadStart(object obj); //参数对象obj
  • 异步回调委托:
    public delegate void AsyncCallback(IAsyncResult ar);
21.对于只用一次,且主体语句数量较少的方法,应该使用Lambda表达式,它通常用于注册给委托、或作为其它方法的参数(参数类型是匹配的委托类型)
22.理解委托的本质:
  • 委托是一个类
  • 委托保存着对注册方法的引用(方法指针),多播委托保存着一组方法指针
  • 执行委托,将按顺序调用方法指针指向的方法
  • 对一个委托实例用”=”赋值一个新的方法指针时,将会调用构造函数实例化一个新的委托对象
  • 所以在实例化一个委托对象之后后,应该时刻记住使用”+=”、”-=”来增加、删除新的方法指针
  • 委托类的方法:Invoke()默认调用、在线程池中启用一个新线程调用BeginInvoke()、停止EndInvoke()
23.事件也是委托,加了event关键字是为了限制委托:
  • 禁止了在包含类外部对委托事件对象使用”=”赋值,确保不会被覆盖或赋值为null
  • 禁止了在包含类外部对委托事件对象的直接调用,事件的调用应该是包含类的责任
  • 参数1是触发者对象的引用,参数2是EventArgs或其派生类的对象(可包含一些将在事件触发时需要用到的数据)
24.当委托和Lambda小心闭包对象

(特别是在循环体中的循环变量,对于C#5.0的foreach则不必担心)
- 当Lambda表达式引用了局部变量时,编译器就会自动创建一个闭包对象(如TempClass),该对象的成员包含一个对局部变量的引用(如TempClass.i)、和一个与Lambda表达式等价的方法(如TempClass.add,该方法持有对局部变量的引用)。
- 而该闭包对象中的方法成员TempClass.add最终被赋给了委托(如MyDel),而委托通常在局部变量的作用域之外才执行。
也就是说,委托中注册的方法持有了对局部变量的引用,形成了像JavaScript中的闭包一样的效果,执行委托方法时,局部变量的值将是最新值,而不是给委托注册方法时的局部变量值。

public static void Main()
    {
        Action act=new Action(()=>Console.WriteLine("Begin"));
        for (int i = 0; i < 5; i++)
        {
            act += () => Console.WriteLine(i.ToString());
        }
        act(); //Begin 5 5 5 5 5  因为委托方法持有了对i的引用,当前i的值为5
        Console.ReadKey();
    }
public static void Main()
    {
        Action act=new Action(()=>Console.WriteLine("Begin"));
        for (int i = 0; i < 5; i++)
        {
            int temp = i; //每次都用一个新的temp变量来保存当前的iact += () => Console.WriteLine(temp.ToString());
        }
        act(); //Begin 0 1 2 3 4
        Console.ReadKey();
    }
25.赋值为null,大部分情况下不能提前垃圾回收。
  • 没有必要将没用的实例成员显式赋值为null,因为编译器会忽略该语句。
  • 只有对日后确实没用的静态字段显式赋值为null才有必要,但要确保不会再用到它(或者说不会再用到它的包含类)。
  • 把一个对象赋值为null,它的静态成员不会跟着变为null,因为静态成员跟类的实例无关,它会一直留在内存中,除非显式赋值为null。
后续还有很多其它方面的,如系列化与反系列化,异常处理等,由于篇幅有限,只能等下一篇再发布了
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 网上教学的发展趋势与现状 1 1.2 开发背景 2 1.3 网上答疑辅导系统开发的目的 2 1.4 网上答疑辅导系统开发的意义 2 1.5 本文研究的内容和目标 2 第2章 关键技术介绍 4 2.1 ASP.NET技术介绍 4 2.1.1 .NET简介 4 2.1.2 ASP.NET简介 4 2.2 SQL Server 2000 6 2.3 IIS 5.0 7 2.4 系统开发环境介绍 7 第3章 系统分析 8 3.1 可行性分析 8 3.1.1操作上的可行性 8 3.1.2技术上的可行性 8 3.1.3时机上的可行性 8 3.1.4管理上的可行性 8 3.2 需求分析 9 3.3 系统的业务流图 10 3.4 数据流程图 10 第4章 总体设计 14 4.1 系统设计 14 4.1.1 目标设计 14 4.1.2 设计思想 14 4.1.3 系统功能分析和设计 15 4.1.3.1 系统功能模块的详细介绍 15 4.1.3.2 系统的逻辑功能划分 16 4.2 数据库的实现 18 4.2.1 数据库的需求分析 18 4.2.2 数据库的概念结构设计 19 4.2.3 数据库的逻辑设计 21 4.2.4 数据库访问的设计 23 第5章 详细设计 25 5.1 主界面设计 25 5.1.1 母版页(MasterPage.aspx)的设计 25 5.1.2 主界面(main.aspx)的设计 25 5.2 登录界面的设计 26 5.3 在线答疑(聊天室)的设计 27 5.3.1 聊天登录 27 5.3.2 保存聊天信息 27 5.3.3 获取聊天信息 28 5.3.4 格式化显示聊天信息 29 5.3.5 设计聊天界面 29 5.3.6 实现聊天功能 29 5.4 留言答疑(留言板)的设计与实现 31 5.4.1 留言板页面设计 31 5.4.2 留言板功能的实现 33 5.5课件学习的设计与实现 34 5.6 上传下载模块的设计与实现 35 5.6.1 上传文件模块的设计与实现 35 5.6.2 下载文件模块的设计与实现 37 5.7公告信息的设计与实现 40 5.8系统后台管理的设计与实现 40 5.8.1 留言管理 41 5.8.2 课件管理 42 5.8.3 公告管理 42 5.8.4 用户管理 42

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值