《编写高质量代码改善C#程序的157个建议》笔记

**重写Equals时也应重写GetHasgCode**
如果对象要作为Dictionary的Key值,那么重写Equals时也应重写GetHashCode。比如下列代码,人的身份ID一样应该就是同一个人,那么我们期望得到的输出是true,true。但是不重写GetHasgCode,得到的输出是true,false。因为Dictionary是根据先Key值的HashCode再根据Equals来查找value。找不到对应的HashCode当然找不出Value。
namespace Tip12
{
    class Program
    {
        static Dictionary PersonValues = new Dictionary();
        static void Main(string[] args)
        {


            Person mike = new Person("500221");
            PersonMoreInfo mikeValue = new PersonMoreInfo() { SomeInfo = "Mike's info" };
            PersonValues.Add(mike, mikeValue);
            //Console.WriteLine(mike.GetHashCode());
            Console.WriteLine(PersonValues.ContainsKey(mike));

            Person mike1 = new Person("500221");
            //Console.WriteLine(mike.GetHashCode());
            Console.WriteLine(PersonValues.ContainsKey(mike1));

            Console.Read();
        }

    }

    class Person : IEquatable
    {
        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;
        }
    }
    class PersonMoreInfo
    {
        public string SomeInfo { get; set; }
    }
}

分情况(比如只是用于输出)可多用linq语句来代替比较器,简化代码

Linq语句看情况用first(),take()来避免不必要的迭代
first()找到第一个,take(n)找到n个就完成。不需要遍历所有

小心闭包陷阱

  static void Main(string[] args)
        {
            List lists = new List();
            for (int i = 0; i < 5; i++)
            {
                Action t = () =>
                {
                    Console.WriteLine(i.ToString());
                };
                lists.Add(t);
            }
            foreach (Action t in lists)
            {
                t();
            }
            Console.Read();
        }
        //输出为55555

因为如果匿名方法引用了某个局部变量,编译器会自动将改引用提升到改闭包对象中,即将for循环中变量i修改为引用闭包对象的公共变量i。等同于

        static void Main(string[] args)
        {
            List lists = new List();
            TempClass tempClass = new TempClass();
            for (tempClass.i = 0; tempClass.i < 5; tempClass.i++)
            {
                Action t = tempClass.TempFuc;
                lists.Add(t);
            }
            foreach (Action t in lists)
            {
                t();
            }
            Console.Read();
        }

        class TempClass
        {
            public int i;
            public void TempFuc()
            {
                Console.WriteLine(i.ToString());
            }
        }

如果想输出01234,可以将闭包对象的产生放在for循环内部。代码的如下

        static void Main(string[] args)
        {
            List lists = new List();
            for (int i = 0; i < 5; i++)
            {
                int temp = i;
                Action t = () =>
                {
                    Console.WriteLine(temp.ToString());
                };
                lists.Add(t);
            }
            foreach (Action t in lists)
            {
                t();
            }
            Console.Read();
        }
        //输出为01234

等同于

        static void Main(string[] args)
        {
            List lists = new List();
            for (int i = 0; i < 5; i++)
            {
                TempClass tempClass = new TempClass();
                tempClass.i = i;
                Action t = tempClass.TempFuc;
                lists.Add(t);
            }
            foreach (Action t in lists)
            {
                t();
            }
            Console.Read();
        }

        class TempClass
        {
            public int i;
            public void TempFuc()
            {
                Console.WriteLine(i.ToString());
            }
        }

泛型协变与逆变
除非考虑到不会用于可变性,否则为泛型参数指定out关键字会拓展其应用,建议在实际编码中永远这样使用。
支持协变(out)的类型参数只能用在输出位置:函数返回值、属性的get访问器以及委托参数的某些位置
支持逆变(in)的类型参数只能用在输入位置:方法参数或委托参数的某些位置中出现。

释放资源,实现IDisposable接口

  public class SampleClass : IDisposable
    {
        //演示创建一个非托管资源
        private IntPtr nativeResource = Marshal.AllocHGlobal(100);
        //演示创建一个托管资源
        private AnotherResource managedResource = new AnotherResource();
        private bool disposed = false;

        /// 
        /// 实现IDisposable中的Dispose方法
        /// 
        public void Dispose()
        {
            //必须为true
            Dispose(true);
            //通知垃圾回收机制不再调用终结器(析构器)
            GC.SuppressFinalize(this);
        }

        /// 
        /// 不是必要的,提供一个Close方法仅仅是为了更符合其他语言(如
        /// C++)的规范
        /// 
        public void Close()
        {
            Dispose();
        }

        /// 
        /// 必须,防止程序员忘记了显式调用Dispose方法
        /// 
        ~SampleClass()
        {
            //必须为false
            Dispose(false);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
            {
                return;
            }
            if (disposing)
            {
                // 清理托管资源
                if (managedResource != null)
                {
                    managedResource.Dispose();
                    managedResource = null;
                }
            }
            // 清理非托管资源
            if (nativeResource != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(nativeResource);
                nativeResource = IntPtr.Zero;
            }
            //让类型知道自己已经被释放
            disposed = true;
        }
    }

    class AnotherResource : IDisposable
    {
        public void Dispose()
        {
        }
    }

标准的协作式取消,CancellationTokenSource

        static void Main(string[] args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            Thread t = new Thread(() =>
            {
                while (true)
                {
                    if (cts.Token.IsCancellationRequested)
                    {
                        Console.WriteLine("线程被终止!");
                        break;
                    }
                    Console.WriteLine(DateTime.Now.ToString());
                    Thread.Sleep(1000);
                }
            });
            t.Start();
            Console.ReadLine();
            cts.Cancel();

        }

Lazy模式的单例

    class A
    {
        static readonly Lazy fooLazy;
        private A() { }
        static A() { fooLazy = new Lazy(() => new A()); }
        public static A instance { get { return fooLazy.Value; } }
        
    }

跨线程访问控件
使用控件的Invoke方法或BeginInvoke方法,BeginInvoke是异步

if (this.InvokeRequired)
{
    this.Invoke(new Action(() => button1.Enabled = false));//在拥有此控件的基础窗口句柄的线程上执行指定的委托
    button1.Invoke(new MethodInvoker(() => button1.Enabled = false ));
    button1.Invoke(new Action(() => button1.Enabled = false));  // 跨线程访问UI控件
}
else
{
    button1.Enabled = false
}

button1.BeginInvoke(new Action(() => button1.Enabled = false));
//在创建控件的基础句柄所在线程上**异步**执行指定委托

避免在构造方法中调用虚成员
下面的代码会抛异常。因为创建子类对象时,先执行父类构造方法,父类构造方法里调用了虚方法,此时会执行子类override的方法。子类override的方法里又调用了子类的字段,而此时子类的字段还为null

 class Program
    {

        static void Main()
        {
            American american = new American();
            Console.ReadKey();
        }

        class Person
        {
            public Person()
            {
                InitSkin();
            }

            protected virtual void InitSkin()
            {
                //省略
            }
        }

        class American : Person
        {
            Race Race;

            public American()
                : base()
            {
                Race = new Race() { Name = "White" };
            }

            protected override void InitSkin()
            {
                Console.WriteLine(Race.Name);
            }
        }

        class Race
        {
            public string Name { get; set; }
        }

    }

可以加checked在运算溢出时抛出异常System.OverflowException

static void Main(string[] args)
        {
            ushort salary = 65535;
            try
            {
                checked
                {
                    salary = (ushort)(salary + 1);
                }
            }
            catch(Exception ex)
            {

            }
        }

Md5加密
是不可逆的,多对一(小概率,忽略不计)的加密。
比如存储密码时Md5加密后再存储,这样后台也看不到密码。但是可用穷举法破解

        static string GetMd5Hash(string input)
        {
            using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
            {
                return BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(input))).Replace("-", "");
            }
        }

多次使用Md5,增加穷举法破密成本

        static string GetMd5Hash(string input)
        {
            string hashKey = "Aa1@#$,.Klj+{>.45oP";
            using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
            {
                string hashCode = BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(input))).Replace("-", "") + BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(hashKey))).Replace("-", "");
                return BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(hashCode))).Replace("-", "");
            }
        }

还可以验证文件是否被篡改,用文件内容算出对应的Md5,文件哪怕被改动一个字符,再算出的Md5也不一样

        public static string GetFileHash(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("-", "");
            }
        }

对称加密

class Program
    {
        static void Main()
        {
            EncryptFile(@"c:\temp.txt", @"c:\tempcm.txt", "123");
            Console.WriteLine("加密成功!");
            DecryptFile(@"c:\tempcm.txt", @"c:\tempm.txt", "123");
            Console.WriteLine("解密成功!");
        }

        //缓冲区大小
        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;
        }

        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();
                }
            }
        }

        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();
                }
            }
        }

    }

你可能感兴趣的:(c#)