《編寫高質量代碼改善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<Person, PersonMoreInfo> PersonValues = new Dictionary<Person, PersonMoreInfo>();
        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<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;
        }
    }
    class PersonMoreInfo
    {
        public string SomeInfo { get; set; }
    }
}

分情況(比如只是用於輸出)可多用linq語句來代替比較器,簡化代碼

Linq語句看情況用first(),take()來避免不必要的迭代
first()找到第一個,take(n)找到n個就完成。不需要遍歷所有

小心閉包陷阱

  static void Main(string[] args)
        {
            List<Action> lists = new List<Action>();
            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<Action> lists = new List<Action>();
            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<Action> lists = new List<Action>();
            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<Action> lists = new List<Action>();
            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;

        /// <summary>
        /// 實現IDisposable中的Dispose方法
        /// </summary>
        public void Dispose()
        {
            //必須爲true
            Dispose(true);
            //通知垃圾回收機制不再調用終結器(析構器)
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 不是必要的,提供一個Close方法僅僅是爲了更符合其他語言(如
        /// C++)的規範
        /// </summary>
        public void Close()
        {
            Dispose();
        }

        /// <summary>
        /// 必須,防止程序員忘記了顯式調用Dispose方法
        /// </summary>
        ~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<A> fooLazy;
        private A() { }
        static A() { fooLazy = new Lazy<A>(() => 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();
                }
            }
        }

    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章