C#要点

内容摘要

1 数据类型

  1.1表达范围问题

  1.2 数组的长度问题

  1.3 值类型与引用类型

  1.4 匿名类型与隐式类型

  1.5硬编码造成的精度丢失问题

2 控制流语句

  2.1 foreach语句

  2.2 if-else语句

  2.3 for循环

  2.4 Switch语句

3 类

  3.1 配置文件中使用静态字段或静态属性 6

  3.2 虚方法中不要含有业务逻辑

  3.3 指定构造器

  3.4 对比virtual与abstract

  3.5默认初始值

  3.6 readonly

  3.7 构造函数的调用问题

  3.8 静态类和静态成员

  3.9 可访问性

  3.10尽量使用属性而不是字段

  3.11 接口中的属性

4 泛型

  4.1string.Join方法不能识别泛型

5 集合

  5.1 关于集合的标准查询运算符

  5.2 提高集合插入性能

6 异常处理

  6.1 优先考虑在最外层捕获异常

  6.2 try...finally与return

7 扩展

  7.1对System.Linq的行为进行扩展

1 数据类型

1.1表达范围问题

int类型只能表达-232 至232 -1范围内的数据,float和double比int范围要宽的多,所以在使用这些类型时注意挑选合适的类型使用,另外与金融计算相关时使用decimal。

1.2 数组的长度问题

定义一个数组时最大的长度是多少?理论上最大长度为int.MaxValue。对于32位有符号整数来说最大值为2147483647,64位有符号整数最大值为9223372036854775807。一般来讲不会定义太长的数组,因为这样会比较消耗内存。

1.3 值类型与引用类型

string为引用类型,但下面的方法不会改变其自身:

string str =”abc”;

str.ToUpper();

上面的代码不会将其转为大写,下面写法可行:

str = str.ToUpper();

1.4 匿名类型与隐式类型

C#的匿名类型没有名称,是由编译器动态生成的数据类型,但它仍然是强类型。对匿名类型来说,不可能指定数据类型,所以声明匿名类型变量要使用var。

使用var来声明隐式类型。但对于数据类型并非匿名类型的情况下,建议使用显示数据类型。

var anonymous1 = new { Field1 = "sss", Field2 = "bbb" };

var anonymous2 = new { Field1 = "sss", Field2 = "CCC" };

var anonymous3 = new { Field1 = "ttt" };

var iy = "string";

无法将anonymous1与anonymous3互相赋值,无法将nim 与iy互相赋值,但anonymous1与anonymous2可以互相赋值

1.5硬编码造成的精度丢失问题

如果输入的数字字面值是含有小数,那么计算时默认为double类型,不含有小数,则认为是int类型;以f,d,m结尾的数被认为是float,double,decimal。所以涉及的相关运算时,注意写法。例如:

//运算结果为1.0

float res = 3 / 2;

//运算结果为1.5

float ress = 3f / 2f;

//这是错误写法,因为2.2这种写法是一个double类型

float f = 2.2;

//最后一个字符为f或F则表示float类型

float ff = 2.2f;

//这是错误写法,因为2.2是double类型,所以运算结果为double类型,无法将double类型隐式转换为float类型

//强制转换可以,但是可能会造成精度丢失。

float fff = 1 / 2.2;

2 控制流语句

2.1 foreach语句

使用foreach语句操作集合,禁止循环操作过程中修改集合中的元素。

try
{
  List<string> list = new List<string> { "first", "second", "administrator", "letter",         "join" };
  foreach (var item in list)
  {
    if (item.Contains("l")) list.Remove(item);
  }
}
catch (Exception ex)
{
  Console.WriteLine(ex.Message);
}

异常信息:集合已修改;可能无法执行枚举操作。

异常类型:System.InvalidOperationException

2.2 if-else语句

if(condition1){}

else if(condition2){}

else if(condition3){}

else{}

与之等价的写法更好理解。

if(condition1)

else

{

  if(condtion2)

  else

  {

    if(condition3){}

    else{}

  }

}

2.3 for循环

一般循环的循环变量为int型,但是其他类型如float,double等也是可以使用的。

2.4 Switch语句

常常将Switch用作单一匹配,但不要忘记其多匹配功能,如下面的代码:

string sign ="b";
switch (sign)
{
     case "a":
     case "b":
           Console.WriteLine("多匹配方式");
         break;
     case "z":
         break;
     default:
         break;
}

还可以向下面这样,使用Switch和return组合。

    private int Switch()
     {
            int i = 0;
            switch (i)
            {
                case 0:
                    {
                        return 0;
                    }
                case 1:
                    {
                        return 1;
                    }
                default:
                    {
                        return 2;
                    }
            }
      }

3 类

3.1 配置文件中使用静态字段或静态属性

静态变量是在静态变量所属类初次使用时被初始化的,当静态字段被初始化后,之后每次调用获得的值都是初始化时赋给静态字段的值,除非在这个过程中显示地给静态字段赋值。而静态属性的某些行为类似于静态方法。如下例:

    public class Sys
    {
        /// <summary>
        /// 执行时间
        /// </summary>
        public static string ExecuteTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

        public static string ExecuteDate
        {
            get
            {
                return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            }
            set { }
        }
    }
     static void Main(string[] args)
        {
            Console.WriteLine("字段:"+Sys.ExecuteTime);

            Console.WriteLine("属性:"+Sys.ExecuteDate);

            Thread.Sleep(2000);

            Console.WriteLine("字段0:" + Sys.ExecuteTime);

            Console.WriteLine("属性0:" + Sys.ExecuteDate);

            Thread.Sleep(2000);
            Sys.ExecuteDate = DateTime.Now.ToString("yyyy");
            Console.WriteLine("字段1:" + Sys.ExecuteTime);

            Console.WriteLine("属性1:" + Sys.ExecuteDate);
            Console.Read();

        }

运行上述代码,输出结果如下:

由输出结果可以得出:上面的用法中,静态字段每次调用获得的值都是同一个,即初始化时所赋的值;而调用静态属性每次获得的值都是不同的,每次调用都执行一次get方法。

3.2 虚方法中不要含有业务逻辑

使用virtual修饰符修饰类的方法,那么这个方法就可以在派生类中重写,如果原来的方法包含业务逻辑,派生类重写这个方法后,由于派生类将父类中的虚方法完全覆盖,导致虚方法中的业务逻辑永远不会被执行。

3.3 指定构造器

为了避免因缺少可供访问的默认构造器而造成错误,要在派生类构造器的头部显示指定要运行哪一个基类构造器。

3.4 对比virtual与abstract

为支持重写,基类中必须为要在子类中重写的成员之前添加virtual修饰符,子类成员要标记为override。

使用abstract定义抽象方法。抽象方法没有具体实现,必须在子类方法中实现抽象方法。

虚方法是可以有具体实现的,不过具体实现会在子类的重载中被覆盖。

3.5默认初始值

字段或属性默认初始值随类型的不同而不同。

bool默认初始值为false,对象类型默认初始值为null,int类型为0,float和double为0.0,char为\0。

3.6 readonly

readonly只能用于字段,它指明被其修饰的字段只能在构造函数中修改,或在声明时指定。但对于数组来说情况稍有不同,使用此修饰符修饰数组,那么不允许使用new运算符创建同一个数组的新实例,但可以修改数组中的元素,即使这样的操作不是在构造函数中进行的。

3.7 构造函数的调用问题

如果实例化一个子类,那么子类的构造函数及其父类的构造函数的调用过程是怎么样的呢?调用具有怎样的层次关系?如果父类有几个重载的构造函数而子类未指定构造函数,那么将调用父类的哪一个构造函数呢?

对于继承中涉及到的构造函数的调用问题是比较复杂的。

首先,父类的构造函数先于子类的构造函数被调用。

其次,如果父类和子类都没有自定义构造函数,那么调用的都是默认的构造函数。

    public class Child : Father
    {
        public Child()
        {
            Console.WriteLine("Call Child()");
        }
    }

    public class Father
    {
        public Father()
        {
            Console.WriteLine("Call Father()");
        }

        public Father(string ss)
        {
            Console.WriteLine("Call Father(String)"+ss);
        }
   }

//实例化

Child c = new Child();

输出:

若在Child 中指定调用父类的构造函数:

  public Child():base("f")

        {

            Console.WriteLine("Call Child()");

        }

输出:

结论:从中可看出父类构造函数先于子类构造函数被调用,若子类构造函数不指定调用哪一个,一般会根据参数自动匹配。

3.8 静态类和静态成员

1)静态类是密封的,因此不可被继承。

2)静态类不能包含实例构造函数,但仍可声明静态构造函数以分配初始值或设置某个静态状态。

3)静态方法和属性只能访问静态字段和静态事件。

4)静态成员在第一次被访问之前并且在任何静态构造函数(如调用的话)之前初始化。

5)静态构造函数有以下特点:

l 静态构造函数既没有访问修饰符,也没有参数。

l 在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数来初始化类。

l 无法直接调用静态构造函数。

l 在程序中,用户无法控制何时执行静态构造函数。

l 静态构造函数的典型用途是:当类使用日志文件时,将使用这种构造函数向日志文件中写入项。

l 静态构造函数在为非托管代码创建包装类时也很有用,此时该构造函数可以调用 LoadLibrary 方法。

3.9 可访问性

非嵌套类型:不嵌套在其他类型中的顶级类型的可访问性只能是 internal 或 public。 这些类型的默认可访问性是 internal。

嵌套类型:嵌套类型的可访问性取决于它的可访问域,该域是由已声明的成员可访问性和直接包含类型的可访问域这二者共同确定的。 但是,嵌套类型的可访问域不能超出包含类型的可访问域。

属于

默认的成员可访问性

该成员允许的声明的可访问性

enum

public

class

private

public protected internal private protected internal

interface

public

struct

private

public internal private

注:

1)访问修饰符internal:只有在同一程序集的文件中,内部类型或成员才是可访问的。

访问修饰符protected internal:访问仅限于从包含类派生的当前程序集或类型。

3.10尽量使用属性而不是字段

可以对属性进行更灵活的控制,所以应尽量使用属性,例如下面的代码:

     //只有在类内部可以设置属性值
        public string FileName { private set; get; }

        //可以将计算步骤放在get中
        public int FileSize 
        {
            get 
            {
                int size = 0;
                return size;
            }
        }

3.11 接口中的属性

在接口中定义属性,若只包含Get块,那么接口的实现类中也只能包含Get块。

例如:

  public class Child : Face
    {
        public string Field
        {
            get { throw new NotImplementedException(); }
        }
    }
    public interface Face
    {
        string Field
        {
            get;
        }
  }

4 泛型

4.1string.Join方法不能识别泛型

string JoinStr<T>(T set)

{

  Return string.Join(“,”,set);

}

上面这个方法不能正确返回拼接后的字符串,正确的方式如下:

string JoinStrRight(IEnumerable<string>  set)
{
  Return string.Join(“,”,set);
}

5 集合

5.1 关于集合的标准查询运算符

1)First, FirstOrDefault,Single,SingleOrDefault

First:查找第一个符合条件的元素,如果没有找到,抛异常。

FirstOrDefault:查找第一个符合条件的元素,如果没有找到,返回null。

Single:找到符合条件的一个元素,如果找不到,抛异常;如果有多个元素符合条件,抛异常。

SingleOrDefault:找到符合条件的一个元素,如果找不到,返回null;如果有多个元素符合条件,抛异常。

结论:

如果集合中可能只有一个或没有符合条件的元素,用FirstOrDefault和SingleOrDefault都可,不建议用First和Single,因为要自己处理异常。

如果集合中可能有多个或没有符合条件的元素,建议使用FirstOrDefault。

2)Select

Select为将集合中的元素“映射”为其他形式,而不是筛选出符合某一条件的元素。

3)Except<T>

假设有两个类型为List<string>集合,list1和list2。

list1.Except(list2);返回结果为从list1中去掉list1和list2相同的元素后剩余的部分;

list2.Except(list1);返回结果为从list2中去掉list1和list2相同的元素后剩余的部分;

若要求两个集合相同的部分,方法为:

List<string> list3 = list1.Except(list2);

List<string> same = list1.Except(list3);

若要求两个集合的并集且无重复,方法为:

List<string> listA = list1.Except(list2);

List<string> listB = list2.Except(list1);

List<string> all = listA.AddRange(listB);

5.2 提高集合插入性能

对于List<T>:

如果集合大小已经小于集合默认容量,此方法为复杂度为 o (1) 操作。如果需要增加以容纳新元素的容量,此方法将变为 O (n) 操作,其中 n 是数组大小。

对于LinkedList:

LinkedList.AddFirst(),AddLast()的运算复杂度为 O(1)。

对于Stack和Queue:

如果集合大小已经小于集合默认容量,此方法为复杂度为 o (1) 操作。如果需要增加以容纳新元素的容量,此方法将变为 O (n) 操作,其中 n 是数组大小。

结论:如果能事先确定大小,则确定大小。

6 异常处理

6.1 优先考虑在最外层捕获异常

假设FunctionA,FunctionB,FunctionC三个方法,FunctionA调用了FunctionB,FunctionB调用了FunctionC,那么首先考虑对FunctionA使用try...catch语句。

6.2 try...finally与return

finally块中的语句总会执行,除了finally语句块中的语句抛异常以外。

       try 
            {
                Console.WriteLine("Execute try");
                return;
            }
            catch {
                return;
            }
            finally
            {
                Console.WriteLine("Execute finally");
            }

输出:

7 扩展

7.1对System.Linq的行为进行扩展

扩展方法:

public static class Extend
{
        public static string ToString(this string input, int str)
        {
            return "输入的是:" + str;
        }
}

Main方法中调用:

using System;

namespace ConsoleApp_CSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            string str = "输入";
            Console.WriteLine(str.ToString(1));
            Console.Read();
        }
        
    }
}

编译Main方法,无法编译通过,错误信息如下:

原因是:未引入扩展方法所在命名空间。在调用扩展方法时,引入扩展方法的命名空间即可,即使用如上例中使用将using ConsoleApp_CSharp.Extend;语句引入。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏游戏开发那些事

【小白学C#】浅谈.NET中的IL代码

  前几天群里有位水友提问:”C#中,当一个方法所传入的参数是一个静态字段的时候,程序是直接到静态字段拿数据还是从复制的函数栈中拿数据“。其实很明显,这和方法参...

672
来自专栏青玉伏案

窥探Swift之别具一格的Struct和Class

  说到结构体和类,还是那句话,只要是接触过编程的小伙伴们对这两者并不陌生。但在Swift中的Struct和Class也有着令人眼前一亮的特性。Struct的功...

1867
来自专栏Android Note

Java 8之lambda表达式(二)

784
来自专栏菩提树下的杨过

ruby学习笔记(9)-别名(alias)与方法取消(undef,remove_method)

先来看别名,ruby中可以给方法或全局变量起一个别名,有意思的是:方法别名定义后,即使对应的方法在后面的代码中重新定义(即修改内部实现)后,别名仍然可以调用到修...

1816
来自专栏腾讯Bugly的专栏

Swift 对象内存模型探究(一)

HandyJSON 是 Swift 处理 JSON 数据的开源库之一,类似 JOSNModel,它可以直接将 JSON 数据转化为类实例在代码中使用。 由于 S...

4237
来自专栏大闲人柴毛毛

JavaScript奇淫技巧(一)

if-else简化写法 代码中若出现多层if-else嵌套,代码就会显得臃肿不堪,这时可采用替代方案来浓缩代码。 常规写法:(臃肿不堪) if(){ ...

3376
来自专栏一“技”之长

Swift解读专题四——字符串与字符 原

        Swift中提供了String类型与Characters类型来处理字符串和字符数据,Swift中的String类型除了提供了许多方便开发者使用的...

572
来自专栏逸鹏说道

Python3 与 C# 基础语法对比(Function专栏-新排版)

在线编程:https://mybinder.org/v2/gh/lotapp/BaseCode/master

723
来自专栏C#

C#泛型方法解析

    C#2.0引入了泛型这个特性,由于泛型的引入,在一定程度上极大的增强了C#的生命力,可以完成C#1.0时需要编写复杂代码才可以完成的一些功能。但是作为开...

1719
来自专栏Java爬坑系列

【Java入门提高篇】Day8 Java内部类——匿名内部类

  今天来看看另一个更加神奇的类——匿名内部类。   就像它的名字表示的那样,这个类是匿名的,用完之后,深藏功与名,就像扫地僧那样默默潜藏于深山之中。匿名内部类...

18810

扫描关注云+社区