写更好的CSharp代码
起因
在项目中发现一些不太好的用法,特意记录下来.
1. 类型转换,is/as
/// <summary>
/// 通过is判断类型,然后强制转换类型
/// </summary>
/// <param name="people"></param>
/// <returns></returns>
public People TestIsType(object people)
{
if (people is People)
{
return (People)people;
}
else
{
return null;
}
}
/// <summary>
/// 通过as进行类型转换,只需转换后判断是否为null
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public People TestAsType(object obj)
{
People people = obj as People;
if (people != null)
{
return people;
}
else
{
return null;
}
}
上面分别是通过is和as判断类型和转换类型,在一个winform项目中,看到大量先是通过is判断类型,然后再通过as进行类型转换,其实是没必要的,个人更倾向于直接用as进行类型转换。通常情况下as类型转换更快些。
下面是通过分别通过is和as类型转换10000次,。Net Core 3.0 preview6测试结果:
测试代码
[Benchmark]
public void TestIs()
{
for (int i = 0; i < 10000; i++)
{
var p = TestIsType(obj);
}
}
[Benchmark]
public void TestAs()
{
for (int i = 0; i < 10000; i++)
{
var p = TestAsType(obj);
}
}
2. 缓存委托
//定义委托
public delegate int MathOp(int x, int y);
private static int Add(int x, int y)
{
return x + y;
}
private static int DoOperation(MathOp mathOp, int x, int y)
{
return mathOp(x, y);
}
private static void Main(string[] args)
{
//委托也是一种类型
//一. 使用次数少的时候,可以使用
//1. 通过构造函数,创建实例
//IL_0008: newobj instance void Program/MathOp::.ctor(object, native int)
//IL_000d: ldc.i4.1
//IL_000e: ldc.i4.2
//2. 调用
//IL_000f: call int32 Program::DoOperation(class qiufeng.core.app.Program/MathOp, int32, int32)
for (int i = 0; i < 10000; i++)
{
DoOperation(Add, 1, 2);
}
//二. 使用频繁,将委托缓存下来使用
//只实例化一次
//IL_0008: newobj instance void Program / MathOp::.ctor(object, native int)
//IL_000d: stloc.0
//IL_000e: ldc.i4.0
//IL_000f: stloc.1
//IL_0010: br.s IL_0021
//.loop
//{
// IL_0012: nop
// IL_0013: ldloc.0
// IL_0014: ldc.i4.1
// IL_0015: ldc.i4.2
//// 多次调用
// IL_0016: call int32 Program::DoOperation(class Program/MathOp, int32, int32)
// IL_001b: pop
// IL_001c: nop
// IL_001d: ldloc.1
// IL_001e: ldc.i4.1
// IL_001f: add
// IL_0020: stloc.1
// IL_0021: ldloc.1
// IL_0022: ldc.i4 1000
// IL_0027: clt
// IL_0029: stloc.2
// IL_002a: ldloc.2
// IL_002b: brtrue.s IL_0012
//}
MathOp op = Add; //op引用创建委托实例(缓存)
for (int i = 0; i < 10000; i++)
{
DoOperation(op, 1, 2); //这里只有调用开销
}
Console.ReadKey();
}
将上面的代码,稍微调整一下进行性能测试,这里就不展示测试代码了,看结果:
3. 指定集合初始化大小
List/Queue/Stack这几个集合,内部采用连续的数组实现,当空间不够用的时候,内部会进行重新分配内存(当前大小*2),将内部数组拷贝到新分配数组中,由GC进行回收.
[Benchmark]
public void ListDefaultCapacity()
{
var list = new List<int>();
for (int i = 0; i < Count; i++)
{
list.Add(i + 1);
}
}
[Benchmark]
public void ListSetCapacity()
{
var list = new List<int>(Count);
for (int i = 0; i < Count; i++)
{
list.Add(i + 1);
}
}
4. 不使用魔数
/// <summary>
/// 检查信息
/// </summary>
public class CheckInfo
{
/// <summary>
/// 检查Id
/// </summary>
public int CheckId { get; set; }
/// <summary>
/// 检查状态
/// </summary>
public int State { get; set; }
}
static void Main(string[] args)
{
var checkInfo = new CheckInfo();
checkInfo.State = 10; //10 魔数 代表什么意思呀?
}
将State属性改为枚举.
public enum CheckState
{
/// <summary>
/// 待检
/// </summary>
Waiting = 10,
/// <summary>
/// 检毕
/// </summary>
Over = 20,
}
/// <summary>
/// 检查信息
/// </summary>
public class CheckInfo
{
/// <summary>
/// 检查Id
/// </summary>
public int CheckId { get; set; }
/// <summary>
/// 检查状态
/// </summary>
public CheckState State { get; set; }
}
static void Main(string[] args)
{
var checkInfo = new CheckInfo();
checkInfo.State = CheckState.Waiting;
}
5. 文件名统一处理
如果考虑跨平台的话,比如服务端放在Linux服务器上,文件名最好还是统一处理.因为在Unix/Linux/MacOS文件名都区分大小写.个人觉得所有涉及到文件名的.都统一改为小写.涉及到文件名的话,又扯出目录符.因为Windows目录符'/'和'\'都支持,其他平台是'/',所以也建议改为'/'.
文件编码统一为UTF-8.
6. 资源管理
先说文件使用完毕之后要记得关闭或者释放,在项目中遇到过因为文件使用之后,没有关闭和释放,造成文件占用触发的异常FileStream fileStream = null;
try
{
fileStream = new FileStream("1.txt", FileMode.Open, FileAccess.Read, FileShare.Read);
//业务逻辑处理
//xxxxx
}
finally
{
if (fileStream != null)
{
fileStream.Dispose(); //fileStream.Close();
}
}
因为这样写,还是有些繁琐,于是便有了语法糖,使用using.
//使用using 管理文件,using 相当于try finally
using (FileStream fileStream = new FileStream("1.txt", FileMode.Open, FileAccess.Read, FileShare.Read))
{
//处理逻辑
}
7. 慎重使用正则表达式
正则表达式虽然使用起来很方便,但在性能要求高的时候应慎重使用.//1.RegexOptions.Compiled 指定将正则表达式编译为程序集.这会产生更快的执行速度,但会增加启动时间
Regex regex = new Regex("^[0-9]+$",RegexOptions.Compiled);
//2. 重复使用已经创建的Regex对象.
regex.Match(str);
8. 函数参数不要过多,参数如果达到7个以上,可以考虑封装为实体了
在项目中,我见过一些函数,参数个数有二三十个,说实话看到参数这么多,本能就想放弃这个了.内心的第一想法要不要自己重新写一个呢?这里模拟这种函数.
/// <summary>
/// 修改病人信息
/// 这里只是模拟,下面参数名没有写注释,在项目中一定要写
/// </summary>
/// <param name="x1"></param>
/// <param name="x2"></param>
/// <param name="x3"></param>
/// <param name="x4"></param>
/// <param name="x5"></param>
/// <param name="x6"></param>
/// <param name="x7"></param>
/// <param name="x8"></param>
/// <param name="x9"></param>
/// <param name="x10"></param>
/// <param name="x11"></param>
/// <param name="x12"></param>
/// <param name="x13"></param>
/// <param name="x14"></param>
/// <param name="x15"></param>
/// <param name="x16"></param>
/// <param name="x17"></param>
/// <param name="x18"></param>
/// <param name="x19"></param>
/// <param name="x20"></param>
/// <returns></returns>
public bool SetPatientInfo(string x1, string x2, string x3, string x4, string x5,
string x6, string x7, string x8, string x9, string x10,
string x11, string x12, string x13, string x14, string x15,
string x16, string x17, string x18, string x19, string x20)
{
//业务处理逻辑
//..............
return true;
}
用实体进行重构:
/// <summary>
/// 病人信息
/// </summary>
public class PatientInfo
{
/// <summary>
/// 病人Id
/// </summary>
public int PatientId { get; set; }
/// <summary>
/// 病人姓名
/// </summary>
public string PatientName { get; set; }
//这里省略更多属性
}
/// <summary>
/// 修改病人信息
/// </summary>
/// <param name="patientInfo">病人信息</param>
/// <returns></returns>
public bool SetPatientInfo(PatientInfo patientInfo)
{
//业务处理逻辑
//..............
return true;
}
秋风
2019-06-24