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 条评论
登录 后参与评论

相关文章

来自专栏老马说编程

(91) Lambda表达式 / 计算机程序的思维逻辑

在之前的章节中,我们的讨论基本都是基于Java 7的,从本节开始,我们探讨Java 8的一些特性,主要内容包括: 传递行为代码 - Lambda表达式 函数式...

1998
来自专栏技术博客

C#委托四(匿名方法)

什么是匿名方法? 匿名方法是C#2.0引入的一个新特性,它允许开发者声明自己的函数代码而无须使用委托函数。 C#为委托提供一种机制,可以为委托定义匿名方...

1022
来自专栏草根专栏

C# 7.0简而言之 -- 02. C#基础 (1)

语句1里面计算了表达式(expression) 12 * 30, 并把结果保存到了本地变量x里面, x是整型类型.

38112
来自专栏蘑菇先生的技术笔记

c#语言-高阶函数

3086
来自专栏WindCoder

Java中的域与变量

Java中的Field译为“字段”,也译为“域”,Field和成员变量(Member Variable)是相同的。所以域是变量中的一种。

3841
来自专栏郭耀华‘s Blog

快速排序法

/** * 快速排序实现 * Created by John Kwok on 2018/2/2. */ import java.util.Arrays; ...

3276
来自专栏me的随笔

.NET中可空值类型实现原理

为了让.Net中的值类型可以赋值为null,微软特地添加了Nullable<T>类型,也可简写为T?。但是Nullable<T>自身是结构体,也是值类型,那么它...

862
来自专栏个人随笔

Java 高级开发必修知识---内部类

Java 内部类分为:   1)成员内部类   2)静态嵌套类   3)方法内部类   4)匿名内部类 ? 内部类的共性 1、内部类仍然是一个独立的类,在编译之...

3359
来自专栏Jackson0714

不惧面试:委托

35313
来自专栏林德熙的博客

C# 循环的判断会进来几次

最近有小伙伴告诉我,在循环的判断条件只会计算一次,本金鱼不相信,于是就做了测试,本文记录我做的测试。

993

扫码关注云+社区