zoukankan      html  css  js  c++  java
  • 提高你的C#程序编码质量

      摘自陆敏技之《编写高质量代码:改善C#程序的157个建议》,编写C#程序代码时应考虑代码效率、安全和美观,可参考下述建议。想成为一名合格的搬砖工,牢记吧!!

    基本语言要素

    1、正确操作字符串

       1) 避免装箱操作。如语句:String str = "hans"+8 就存在装箱操作,建议改成语句:String str = "hans"+8.ToString()

       2) 使用StringBuilder代替String运算(经测试,当执行5000次加运算时,StringBuilder效率是String的近600倍)。C#中String一旦被赋值不可改变,进行任何操作(+,=)都会在内存中创建一个新的字符串对象,会给运行计算带来额外开销。而StringBuilder并不会重新创建一个新的String对象,StringBudiler每次执行+操作时,如果内容空间不够(默认长度16),会重新加倍进行分配空间。

    //耗时3000毫秒
    String str = "";
    for (int i = 0; i < 50000; i++) {
        str += i.ToString();
    }
    
    //耗时5毫秒
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 50000; i++)
    {
        sb.Append(i.ToString());
    }
    View Code

       3) 常使用String.Format方法。 String.Format在内部使用了StringBuilder方法格式化字符串,效率很高,且代码美观,可读性高。

    2、类型转换

       1) 常使用as转换类型。as转换类型效率高,类型转换失败且不会报异常,而是值为null。

       2) 使用TryParse代替Parse。Parse转型失败会引发异常,异常过程会消耗性能,而TryParse转型失败无异常,out操作符将参数设置为0。经测试,当转型失败时,TryParse效率要比Parse高几百倍

       3) 使用int?使得值类型也可以为nul。T?是Nullable<T>的简写,值可以为nul。T?判断是否为nul可用简写操作符??,如int? i=22;int j = i ?? 0。

    3、区别readonlyconst使用方法。const是一个编译期常量,readonly是一个运行时常量。const在编译时,会将常量用对应的值替代,运行效率高,而readonly在运行时初始化,初始化后不可修改,运行效率比const低,但是灵活性高。readonly赋值发生在运行时,赋值后不可改变表示:1) 值类型,只本身不可改变 2) 引用类型,引用指针不可改变,即不可修改指针指向新对象,但对象内容可修改

    4、避免给enum枚举类型的元素提供显示的值。在如下枚举类型Week中增加一个元素,输出ValueTemp的值等于Wednesday,原因是ValueTemp定义时没有赋值,编译期会逐个为元素值+1,当编译器发现ValueTemp时,会在Tuesday = 2的基础上+1,所以ValueTemp实际赋值为3,与Wednesday=3相等。

    enum Week { Monday = 1, Tuesday = 2, ValueTemp, Wednesday = 3, Thursday = 4, Friday = 5, Saturday = 6, Sunday = 7 }

    5、熟悉运算符重载。运算符重载可使得对象运算操作简洁方便,参考下例:

    class Salary
    {
        public int RMB { get; set; }
    
        //运算符重载
        public static Salary operator +(Salary s1, Salary s2) {
            s2.RMB += s1.RMB;
            return s2;
        }
    }
    
    //调用
    Salary s1 = new Salary() { RMB = 3 };
    Salary s2 = new Salary() { RMB = 5 };
    Salary s3 = s1 + s2;   //运用运算符重载
    View Code

    6、实现深拷贝浅拷贝。浅拷贝是将对象中的所有字段复制到新的对象中,其中,值类型字段拷贝后副本的修改不会影响源对象对应值,而引用字段拷贝后与原字段指向同一对象地址,副本值修改后会影响源对象对应的值(参考《C#值类型与引用类型区别》);深拷贝是将对象中的所有值类型和引用类型字段复制到新对象中,引用类型字段重新创建引用对象,副本的修改不影响源对象对应值。

    //浅拷贝 继承ICloneable接口并实现Clone方法
    class Salary : ICloneable
    {
        public int RMB { get; set; }
    
        public object Clone()
        {
            //实现浅拷贝
            return this.MemberwiseClone(); 
        }
    }
    
    //深拷贝,通过对象序列化和反序列化实现,继承接口ICloneable并实现方法Clone
    [Serializable]
    class Salary : ICloneable
    {
        public int RMB { get; set; }
    
        public object Clone()
        {
            using (Stream objectStream = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(objectStream, this);
                objectStream.Seek(0, SeekOrigin.Begin);
                return formatter.Deserialize(objectStream) as Salary;
            }
        }
    }
    View Code

    7、使用dynamic简化反射操作。 dynamic是Framework 4.0的新特性,可以使C#具有弱语言的特性。

       1) var与dynamic的区别:var在编译的时候替换成自动匹配的实际类型,而dynamic被编译后实际上是一个Object类型,只是编译期会进行特殊处理,在编译器不进行任何的类型检查,而是将类型检查放到了运行期。 

       2) 反射的优化,参考下例:

    class A
    {
        public String Name { get; set; }
    
        public int Add(int a, int b)
        {
            return a + b;
        }
    }
    
    //调用
    A a1 = new A();
    MethodInfo m = a1.GetType().GetMethod("Add");
    
    //普通反射 耗时1084ms
    for (int i = 0; i < 1000000; i++)
    {
        int re = (int)m.Invoke(a1, new object[] { 3, 4 });
    }
    
    //优化后的反射 耗时13ms
    var delg = (Func<A, int, int, int>)Delegate.CreateDelegate(typeof(Func<A, int, int, int>), m);
    for (int i = 0; i < 1000000; i++)
    {
        delg(a1, 3, 4);
    }
    
    //使用dynamic优化反射 耗时60ms
    dynamic a2 = new A();
    for (int i = 0; i < 1000000; i++) {
        a2.Add(3, 4);
    }
    
    //使用dynamic优化反射 耗时60ms
    dynamic a2 = new A();
    for (int i = 0; i < 1000000; i++) {
         a2.Add(3, 4);
    }
    View Code

    8、使用Environment.NewLine获取当前环境下的换行符号。

    9、使用params减少重复参数。如方法:public void pap(String a,String b,String c){ } 可简写为:public void pap(params String[] args){ }。注意:① params数组必须是方法的最后一个参数 ② 不允许out或ref数组

    10、扩展类型中的方法。扩展方法是一种特殊的静态方法,可以为类型扩展方法而无需创建新的派生类型。本实例扩展了String类型添加了扩展方法Test():

    //自定义扩展类 必须为静态类
    static class StringExtenstion
    {
        //扩展String类添加方法Test,必须为静态方法,参数格式为:this 类型名称 对象
        //调用方法如:String str="hans"; Console.WriteLine(str.Test());  --输出my string
        public static String Test(this String str) {
            return "my string";
        }
    }
    View Code
    集合和LINQ

    1、对象和集合初始化:Person person = new Person(){ Name="hans",Age = 25 };

    2、匿名类型:var persion = new { Name="hans",Age=25 };  编译器会自动生成具有对应字段的匿名类。

    3、LINQ查询中避免不必要的迭代。充分运用First和Take等方法,查询到符合条件的记录就立即返回,而不是所有结果返回再筛选,效率可大幅度提高。

    资源管理和序列化

    1、继承IDispose接口的类型,实例化可用using语法。using会在结束时,自动调用对象的Dispose方法。

    2、通用BinarySerializer序列化。BinarySerializer.cs:

    class BinarySerializer
    {
    
        //将类型序列化为字符串
        public static string Serialize<T>(T t)
        {
            using (MemoryStream stream = new MemoryStream()) {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, t);
                return System.Text.Encoding.UTF8.GetString(stream.ToArray());
            }
        }
    
        //将类型序列化为文件
        public static void SerializeToFile<T>(T t, string path, string fullName)
        {
            if (!Directory.Exists(path)) {
                Directory.CreateDirectory(path);
            }
            string fullPath = string.Format(@"{0}{1}", path, fullName);
            using (FileStream stream = new FileStream(fullPath, FileMode.OpenOrCreate)) {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, t);
                stream.Flush();
            }
        }
    
        //将字符串反序列化为类型
        public static TResult Deserialize<TResult>(string s) where TResult : class
        {
            byte[] bs = System.Text.Encoding.UTF8.GetBytes(s);
            using (MemoryStream stream = new MemoryStream(bs)) {
                BinaryFormatter formatter = new BinaryFormatter();
                return formatter.Deserialize(stream) as TResult;
            }
        }
    
        //将文件反序列化为类型
        public static TResult DeserializeFromFile<TResult>(string path) where TResult : class
        {
            using (FileStream stream = new FileStream(path, FileMode.Open)) {
                BinaryFormatter formatter = new BinaryFormatter();
                return formatter.Deserialize(stream) as TResult;
            }
        }
    }
    View Code

    3、序列化特性说明: 

       1) Serializable:用于类,指示一个类可以序列化;

       2) NonSerialized:用于字段,指示一个字段不被序列化。因为属性的本质是方法,因此NonSerialized不可直接用于属性,可用于自己实现的属性;      

       3) OnDeserialized:应用于某方法时,会指定在对象反序列化后立即调用此方法;

       4) OnDeserializing:应用于某方法时,会指定在对象反序列化时调用此方法;

       5) OnSerialized:如果将对象图应用于某方法,则应指定在序列化该对象图后是否调用该方法;

       6) OnSerializing:当他应用于某个方法时,会指定在对象序列化前调用此方法;

    异步、多线程、任务和并行

    1、异步与多线程。异步与多线程两者度可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。很多时候,我们分不清异步与多线程的区别,经常经他们混为一谈,其实,他们还是有区别的:

       1) 异步操作本质:所有的程序最终都会由计算机硬件来执行,拥有DMA功能的硬件在和内存进行数据交互的时候可以不消耗CPU资源,这些不消耗CPU时间的I/O操作正是异步操作的硬件基础。所以即使在DOS这样的单进程系统中也同样可以发起异步的DMA操作。优点异步操作无需额外线程负担,并且使用了回调的方式进行处理,在设计良好的情况下,处理函数可尽可能减少共享变量的使用,减少了死锁发生的可能性缺点异步操作编写复杂,回调难以调试。

       2) 线程本质:线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能。线程本质上是进程总一段并发执行的代码,所以线程需要操作系统投入CPU资源来运行和调度。优点编写简单缺点线程使用会消耗额外切换带来的负担,并且线程间共享变量可能造成死锁。

       3) 适用范围:多线程适用于计算密集型工作,异步机制适用于IO密集型工作,详细参考图1。

    图1 单线程、多线程适用条件

       一个使用了异步操作的WinForm程序示例如下所示,点击按钮,异步获取网页源码并显示在窗体的文本控件textBox1上。

    private void button1_Click(object sender, EventArgs e)
    {
        //开辟一个线程
        Thread t = new Thread(()=>{
            var request = HttpWebRequest.Create("http://www.cnblogs.com/hanganglin");
            //发起异步请求
            request.BeginGetResponse(this.AsyncCallbackImpl, request);
        });
        t.Start();
    }
    
    //回调方法
    public void AsyncCallbackImpl(IAsyncResult ar) {
        WebRequest request = ar.AsyncState as WebRequest;
        var response = request.EndGetResponse(ar);
        var stream = response.GetResponseStream();
        using (StreamReader reader = new StreamReader(stream)) {
            var content = reader.ReadToEnd();
            //由于textBox1控件是主线程创建的,在其他线程中需要调用必须采用异步机制
            //如果InvokeRequired为True,则必须通过异步来修改,否则可直接修改
            if (textBox1.InvokeRequired) {
                textBox1.BeginInvoke(new Action(() => {
                    textBox1.Text = content;
                }));
            }
            else {
                textBox1.Text = content;
            }
        }
    }
    View Code

       值得注意的是,创建控件线程以外的线程想访问控件,可通过控件的BeginInvoke异步方法,BeginInvoke方法是将消息发送到消息队列中等待UI所在的线程进行处理,代码:if(textBox1.InvokeRequired){ textBox.BeginInvoke(new Action(()=>{ textBox.Text = content; })); } else { textBox1.Text = content; }

    安全性设计

    1、声明变量时考虑最大值,关键字check可检查运算是否溢出,运算溢出则抛出异常。 代码:check{ ... }。

    2、文件MD5哈希值判断文件内容是否修改。对文件求MD5哈希值,当文件内容被修改后再求MD5哈希值,比较两个值可判断文件内容是否被修改过。

    //获取文件的md5哈希值
    public static String GetFileMd5Hash(String filePath) { 
        using(MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
        using(FileStream fs = new FileStream(filePath,FileMode.Open,FileAccess.Read,FileShare.Read)){
            return BitConverter.ToString(md5.ComputeHash(fs)).Replace("-", "");
        }
    }
    View Code

    3、合适选择使用对称加密非对称加密。对称加密加密和解密时使用了相同的密钥和加密算法,其优点是加密解密速度快,常用于大量数据传输,缺点是传输数据时需要传输密钥, 安全系数不高。非对称加密使用了不同的密钥,公钥PK和私钥SK,用公钥PK进行加密,只有用对应的私钥SK才可以解密,优点是传输加密信息时不需要传输私钥,安全系数高,缺点是算法复杂,加密解密速度很慢。

       C#下的一个文件对称加密示例MySymmetricAlgorithm: 

    public class MySymmetricAlgorithm
    {
    //缓冲区大小
    static int bufferSize = 128 * 1024;
    //密钥salt  防止“字典攻击”
    static byte[] salt = { 134, 216, 7, 36, 88, 164, 91, 227, 174, 76, 191, 197, 192, 154, 200, 248 };
    //初始化向量
    static byte[] iv = { 134, 216, 7, 36, 88, 164, 91, 227, 174, 76, 191, 197, 192, 154, 200, 248 };
    
    //初始化并返回对称加密算法
    static SymmetricAlgorithm CreateRijndael(string password, byte[] salt)
    {
        PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, salt, "SHA256", 1000);
        SymmetricAlgorithm sma = Rijndael.Create();
        sma.KeySize = 256;
        sma.Key = pdb.GetBytes(32);
        sma.Padding = PaddingMode.PKCS7;
        return sma;
    }
    
    public static void EncryptFile(string inFile, string outFile, string password)
    {
        using (FileStream inFileStream = File.OpenRead(inFile), outFileStream = File.Open(outFile, FileMode.OpenOrCreate))
        using (SymmetricAlgorithm algorithm = CreateRijndael(password, salt)) {
            algorithm.IV = iv;
            using (CryptoStream cryptoStream = new CryptoStream(outFileStream, algorithm.CreateEncryptor(), CryptoStreamMode.Write)) {
                byte[] bytes = new byte[bufferSize];
                int readSize = -1;
                while ((readSize = inFileStream.Read(bytes, 0, bytes.Length)) != 0) {
                    cryptoStream.Write(bytes, 0, readSize);
                }
                cryptoStream.Flush();
            }
        }
    }
    
    public static void DecryptFile(string inFile, string outFile, string password)
    {
        using (FileStream inFileStream = File.OpenRead(inFile), outFileStream = File.OpenWrite(outFile))
        using (SymmetricAlgorithm algorithm = CreateRijndael(password, salt)) {
            algorithm.IV = iv;
            using (CryptoStream cryptoStream = new CryptoStream(inFileStream, algorithm.CreateDecryptor(), CryptoStreamMode.Read)) {
                byte[] bytes = new byte[bufferSize];
                int readSize = -1;
                int numReads = (int)(inFileStream.Length / bufferSize);
                int slack = (int)(inFileStream.Length % bufferSize);
                for (int i = 0; i < numReads; ++i) {
                    readSize = cryptoStream.Read(bytes, 0, bytes.Length);
                    outFileStream.Write(bytes, 0, readSize);
                }
                if (slack > 0) {
                    readSize = cryptoStream.Read(bytes, 0, (int)slack);
                    outFileStream.Write(bytes, 0, readSize);
                }
                outFileStream.Flush();
            }
        }
    }
    View Code
    类型设计

    1、区分接口抽象类的应用场合。接口与抽象类的区别:① 接口支持多继承,抽象类只能但继承; ② 接口可以包含方法、属性、索引器、事件的签名,但不能有实现,抽象类则可以通过虚方法来实现; ③ 接口新增方法后,所有继承者必须重构,否则编译不通过,而抽象类新增虚方法后不需要(新增抽象方法也需重构)。由于存在这些区别,接口一旦被设计出来,就应该是不变的,而抽象类可以随着版本的升级增加一些功能。接口与抽象类的应用场景简单可概括为:① 如果对象存在若干功能相近且关系紧密的版本,则使用抽象类; ② 如果对象关系不紧密,但是若干功能拥有共同的声明,则使用接口; ③ 抽象类适合于提供丰富功能的场合,接口则更倾向于提供单一的一组功能

    2、优先考虑组合(Has a),然后考虑继承(Is a)。组合是将其他类型的对象作为本类型的成员使用,而继承是子类继承父类并使用。组合好比"黑盒式代码使用",继承好比"白盒式代码使用"。组合的耦合性比继承更低,封装性比继承更高。

    3、开闭原则。开闭原则是面向对象设计中最重要的原则之一,是可复用设计的基石。开闭原则原话翻译:软件实体应该对扩展开放,对修改关闭。通俗地说,在软件体系扩展新功能时,不应该修改现有的代码

    命名规范

    1、命名术语:

       1) PascalCasing帕斯卡命名法(首字母大写),公开元素建议使用帕斯卡命名法。建议用于命名空间、类型、接口、方法、属性、事件、静态字段和枚举值。

       2) camelCasing驼峰命名法(首字母小写),非公开元素建议使用驼峰命名法。建议用于参数、私有字段和方法内变量。

    2、命名规范:

       1) 命名空间:使用Java中的域名域名命名法,或使用公司名作为前缀,产品名称作为第二层,其他特性作为第三层,如:PanChina.Oa.System。

       2) 类型:使用名词或名词词组进行命名(如UserManager要优于UserManage),不要在类型名前加前缀,派生类名称以基类名称结尾(如Exception所有派生类都以Exception结尾)。

       3) 接口:使用大写字母"I"为前缀,用形容词命名,如IDisposable表示类型可以被释放。

       4) 泛型:使用大写字母"T"为前缀,多个参数使用标号,如T1、T2。

       5) 枚举:枚举类型用复数命名,不要添加如"Enum"或"Flag"等后缀,枚举元素用单数命名。如enum Week { Monday,Tuesdat,.. }

       6) 字段:共有字段使用帕斯卡命名法,私有字段使用驼峰命名法。使用名词或名词词组命名,不添加前缀。

       7) 方法:使用动词词组命名,根据方法对应的任务命名,而不是根据内部实现细节来命名。常用动词:Get、Update、Delete、Add、Validate、Select、Search等,动词后加上动作内容,就是一个规范的方法名。

       8) 属性:用名词或名词词组命名,要用肯定性的短语,如CanSeek,而不是否定短语CantSeek。当属性对应一个类型时,建议则接用类型命名属性名,如:public Company Company{ get;set; },不建议为属性制定另外名字,如TheCompany。

       9) 事件:用动词或动词词组命名(如Cheked、Updated、Selected等词组),以"EventArgs"后缀结尾(绑定事件的方法名加上On)。如:UpdatedEventArgs,绑定事件方法OnUpdated()。

       10) 索引器:固定设计,使用this关键字,如:public String this[int index] { get {return "";} }。

    3、有条件地使用前缀,在.NET设计规范中,不建议使用前缀,如果确有特殊使用需求,建议:① 前缀m_,表示这是一个实例变量 ② 前缀s_,表示这是一个静态变量。

  • 相关阅读:
    如何只通过Sandboxed Solution启动一个定时执行的操作
    创建与SharePoint 2010风格一致的下拉菜单 (续) 整合Feature Custom Action框架
    创建与SharePoint 2010风格一致的下拉菜单
    《SharePoint 2010 应用程序开发指南》第二章预览
    SharePoint 2013 App 开发 (1) 什么是SharePoint App?
    使用Jscex增强SharePoint 2010 JavaScript Client Object Model (JSOM)
    搜索范围的管理
    SharePoint 2010 服务应用程序(Service Application)架构(1)
    SharePoint 2010 服务应用程序(Service Application)架构(2)
    SharePoint 2013 App 开发 (2) 建立开发环境
  • 原文地址:https://www.cnblogs.com/hanganglin/p/3657260.html
Copyright © 2011-2022 走看看