There is nothing permanent except change. 唯变化永恒不变
-[英]赞格威尔
You have to pay for your decision. 你所做的一切决定,都是有代价的,或是性能,或是灵活性,没有最优解,你需要自己去权衡。
-暴王
这些一个个的零散的小点都是在日常工作中总结出来的,似乎没有哪一本编程书会讲这些,所以就总结出来放到这里
小心循环
不要在循环里写循环体公用的初始化方法,应该在循环外只初始化一次
Wrong:
foreach(string t in items) { ListnameList = something.GetList(); if(nameList.Contains(t)) ... }
上面例子可以看到,每次循环都需要请求一次GetList(),并且获取到的List不会被更改,因此每次循环时nameList都是一样的,这时应该把GetList()方法的调用提出循环体
Correct:
ListnameList = something.GetList(); foreach(string t in items) { if(nameList.Contains(t)) ... }
传值 Vs 传引用 Vs 传副本 Vs 传求值方法 Vs 私有字段
传值
优点: 简单
缺点: 任何更改对原值不起作用
string a = "a"; public void Dosomething(string a) { a = "b" } Console.WriteLine(a);
string a = "aaaaa"; string b = a.Replace('a', 'b'); Console.WriteLine(a);
传引用
优点: 更改会对原值起作用,一次改动可以影响所有引用该对象的地方
缺点: 不注意会引起副作用
object c = new object(); object d = c; d = null; Console.WriteLine(c == null);
public class Test { public string Field1 { get; set; } public string Field2 { get; set; } public Test(string field1, string field2) { Field1 = field1; Field2 = field2; } public void Dosomething(Test f) { f.Field1 = "3"; f = null; f = new Test("5", "6"); } } Test e = new Test("1", "2"); e.Dosomething(e); Console.WriteLine(e == null); Console.WriteLine(e.Field1); ???????
传副本
优点: 保护了源数据不被更改
缺点: 序列化反序列化可能造成性能问题
public class ProtectObject { private object valuableObject; public object ValuableObject { get { var objectCopy = JsonSerializer.Serialize(valuableObject); return JsonSerialize.Deserialize<object>(objectCopy); } } }
求值方法
优点: 每次都能得到最新的值
缺点: 每次都重新计算值,可能造成性能问题
class ContactInfo { public string Phonenumber; public string Name; public Address Address; public string AddressStr { get { return string.Format("{0} {1} {2} {4}", City, State, Address1, Address2); } } }
私有字段
优点: 方便
缺点: 不好追踪值是在什么时候被改掉的
空对象模式
通过对缺失对象的封装,以提供默认无任何行为的对象替代品。
namespace NullObjectPattern.Implementation1 { public interface ILog { void Write(string message); } public class ConsoleLog : ILog { public void Write(string message) { Console.WriteLine(message); } } public class NullLog : ILog { public void Write(string message) { // do nothing } } public class Client { public void TestCase1() { ILog log1 = new ConsoleLog(); ILog log2 = new NullLog(); log1.Write("message to log"); log2.Write("message to log"); } } }
封装字段 & 封装对象
数据完整性
Wrong:
class ContactInfo { public string Address 1; public string Address 2; public string State; public string City; public string Phonenumber; public string Name; public GetAddressString() { return string.Format("{0} {1} {2} {4}", City, State, Address1, Address2); } }
Right:
class Address { public string Address 1; public string Address 2; public string State; public string City; } class ContactInfo { public string Phonenumber; public string Name; public Address Address; public string AddressStr { get { return string.Format("{0} {1} {2} {4}", City, State, Address1, Address2); } } }
封装集合(mutable)
public class TestCollection { public ListNumberList { get; private set; } public readonly List ReadOnlyNumberList; } public TestCollection() { NumberList = new List (); ReadOnlyNumberList = new List (); } TestCollection t1 = new TestCollection(); t1.NumberList = new List (); t1.ReadOnlyNumberList = new List&lint>(); t1.NumberList.Add(1); t1.ReadOnlyNumberList.Add(2);
卫语句
函数中的条件逻辑使人难以看清正常的执行途径。使用卫语句表现所有特殊情况。
动机:条件表达式通常有2种表现形式。第一:所有分支都属于正常行为。第二:条件表达式提供的答案中只有一种是正常行为,其他都是不常见的情况。
这2类条件表达式有不同的用途。如果2条分支都是正常行为,就应该使用形如if…..else…..的条件表达式;如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为“卫语句”。卫语句常用于数据校验,并且上面的逻辑“保卫”了下面的逻辑。
void func(void) { if(IsWorkingDay()) { printf("Error,is working day"); } else { if(IsWorkingTime()) { printf("Error ,is working time"); } else { rest(); } } }
使用卫语句
void func() { if(IsWorkingDay()) { printf("Error,is work day"); return; } if(IsWorkingTime()) { printf("Error,is work time"); return ; } rest(); }
保持接口稳定
IContact { SaveContactInfo(int id, string name, string phoneNumber) GetContactInfoById(int id) }
增加了地址
Wrong:
IContact { SaveContactInfo(int id, string name, string phoneNumber, string address1, string address2, string city, string state) GetContactInfoById(int id) }
Right:
IContact { SaveContactInfo(ContactInfo contact) GetContactInfoById(int id) } class ContactInfo { public int Id; public string Name; public string PhoneNumber; public Address AddressInfo; } class Address { public string Address1; public string Address2; public string City; public string State; }
readonly 关键字的局限
我们在日常编程中,如果想指定某个property或field不被外界修改,通常会使用readonly
关键字,这样外界就不能对这个字段赋值了,但是对于对象或者集合来说,这个关键字虽然保证了property或者field本身不能被赋值,但是外部依旧可以修改对象中的property或者field或者修改集合,我们来看下面的代码
我们有个AClass
类,它有一个readonly
的对象AObject
,和一个readonly
的集合List
,当我们在外部调用它时会发现,我们不能够直接对AObject或者List赋值,但是我们可以向List添加元素或者修改AObject的属性值。
Solution
对于集合,.net framework提供了readonly的封装,有很多以IReadOnlyXXX开头的集合接口,
IReadOnlyList
IReadOnlyCollection
IReadOnlyDictionary
因此我们要做两件事来达到集合不被修改的目的
- 用IReadOnlyXXX接口来声明property,
- 声明property为
private set
IReadOnlyList ReadOnlyList
{ get; private set; }
对于对象,参考上面的传副本
部分
[amazon_link asins=’B01LW72R2M,B00P8VZ8T4,B015316YQE’ template=’CopyOf-ProductGrid’ store=’boyd-23′ marketplace=’CN’ link_id=”]
被遗忘的 Where
在代码库里发现了类似这样的代码:
class AObject { public AObject(string str) { this.AString = str; } public string AString; } Listlist = new List () { new AObject("a"), new AObject("b"), new AObject("c") }; list.Where(o => o.AString == "a");
问题:
list.Count?
答案是 3,因为 Where 操作不改变集合本身。如果想用 Where 的结果应该拿 Where 方法的返回值:
list = list.Where(o => o.AString == "a");
Select的误用
在代码库里发现了这样的代码:
class AObject { public AObject(string str) { this.AString = str; } public string AString; } Listlist = new List () { new AObject("a"), new AObject("b"), new AObject("c") }; list.Select(o => { o.AString = "d" return o; });
问题:
list[0].AString?
答案是 “d”,因为 list 里面的元素是引用类型 AObject,Select 应该只做属性的选择或类型转换,不应该改变原集合里面元素的属性,如果要改变元素集合的属性,应该用 Each