2025年3月24日 星期一 甲辰(龙)年 月廿三 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > .net

[C#]代码之坑–编译器不会告诉你的那些事

时间:11-27来源:作者:点击数:59

多读手册!

There is nothing permanent except change. 唯变化永恒不变
-[英]赞格威尔

You have to pay for your decision. 你所做的一切决定,都是有代价的,或是性能,或是灵活性,没有最优解,你需要自己去权衡。
暴王

这些一个个的零散的小点都是在日常工作中总结出来的,似乎没有哪一本编程书会讲这些,所以就总结出来放到这里

小心循环

不要在循环里写循环体公用的初始化方法,应该在循环外只初始化一次
Wrong:

  • foreach(string t in items)
  • {
  • List nameList = something.GetList();
  • if(nameList.Contains(t))
  • ...
  • }

上面例子可以看到,每次循环都需要请求一次GetList(),并且获取到的List不会被更改,因此每次循环时nameList都是一样的,这时应该把GetList()方法的调用提出循环体
Correct:

  • List nameList = 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 List NumberList { 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或者修改集合,我们来看下面的代码
readonly
我们有个AClass类,它有一个readonly的对象AObject,和一个readonly的集合List,当我们在外部调用它时会发现,我们不能够直接对AObject或者List赋值,但是我们可以向List添加元素或者修改AObject的属性值。

Solution
对于集合,.net framework提供了readonly的封装,有很多以IReadOnlyXXX开头的集合接口,

  • IReadOnlyList
  • IReadOnlyCollection
  • IReadOnlyDictionary

因此我们要做两件事来达到集合不被修改的目的

  1. 用IReadOnlyXXX接口来声明property,
  2. 声明property为private set
  • IReadOnlyList ReadOnlyList
  • { get; private set; }

对于对象,参考上面的传副本部分

被遗忘的 Where

在代码库里发现了类似这样的代码:

  • class AObject
  • {
  • public AObject(string str)
  • { this.AString = str; }
  • public string AString;
  • }
  • List list = 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;
  • }
  • List list = 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

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门