C# 查漏补缺(一)

目录

1.局部函数

2.参数数组

3.ref局部变量和ref返回

4.静态构造函数注意事项

5.readonly与const

 6.索引器和属性

7.override&new

 7.1 new复写

7.2 override覆写

8.internal

9.Switch的技巧

10.多维数组例子


1.局部函数

从C#7.0开始,可以在一个方法中声明另一个单独的方法,这样可以将迁入的方法跟其它代码隔离开来,所以它只能在包含它的方法内调用。如果使用恰当,这可以是代码更清晰,更易维护,这些嵌入的方法被称为局部函数

 public class LocalFunction
    {
        public static void MethodWithLocalFunction()
        {
            int MyLocalFunction(int x)
            {
                return x * x;
            }
            int result = MyLocalFunction(10);
            Console.WriteLine($"LocalFunction called: x*x={result}");
        }
    }

2.参数数组

参数数组允许特定类型的零个或者多个实参对应一个特定的形参,规则如下:

  • 在一个参数列表中只能有一个参数数组
  • 如果有,那必须放在最后一个
  • 由参数数组表示的所有参数必须是同一类型
  • 声明时,在数据类型前加params关键字
  • 在数据类型后放置一组空的方括号

数组是引用类型,所以它的所有数据都保存在堆中

 public class Params
    {
        public static void ListInts(string sep,params int[] vals)
        {
            if(vals != null && vals.Length>0)
            {
                for(int i = 0; i < vals.Length; i++)
                {
                    vals[i] *= 10;
                    Console.Write(vals[i]+sep);
                }
            }
        }

        public static void Test()
        {
            int a = 5, b = 6, c = 7;
            ListInts(" --- ", a, b, c);
            Console.WriteLine($"\n {a} {b} {c}");  //不改变参数
        }
    }

3.ref局部变量和ref返回

ref有两个注意事项:

  1. 可以使用这个功能创建一个变量的别名,即使引用的对象是值类型
  2. 对任意一个变量的复制都会反应到另一个变量上,因为他们引用的是相同的对象,即使是值类型。

关于取别名,很好理解:

ref int y=ref x;

不过别名功能不是ref局部变量功能最常见的用途,它经常和ref返回功能一起使用。下面代码演示在方法调用之后,因为调用了修改ref局部变量的代码,所以类的值改变了。

public class RefUsage
    {
        private int score = 5;
        public ref int RefToValue()
        {
            return ref score;
        }
        public void Display()
        {
            Console.WriteLine($"Score: {score}");
        }

        public static void Test()
        {
            RefUsage s=new RefUsage();
            s.Display();
            ref int x=ref s.RefToValue();
            x = 10;
            s.Display();
        }
    }

再举一个更有用的例子:一般函数库中Max函数返回的是值,而不是较大值得引用, 

        public static ref int Max(ref int x,ref int y)
        {
            if(x > y)
                return ref x;
            else
                return ref y;
        }

4.静态构造函数注意事项

通常静态构造函数初始化类的静态字段。初始化类级别的项:

在引用任何静态成员之前

在创建类的任何实例之前

 静态构造函数与实例构造函数不同之处在于:

  • 静态构造函数声明中使用static关键字
  • 类只能有有个静态构造函数,且不能带参数
  • 静态构造函数不能有访问修饰符

其它重要点有:

  • 类既可以有静态构造函数也可以有实例构造函数
  • 如图静态方法,静态构造函数不能访问类的实例成员,因此不能使用this访问器
  • 不能从程序中显示调用他们,系统会自动调用

5.readonly与const

      字段可以用readonly修饰符声明,其作用类似于将字段声明为const,一旦值被设定就不能改版。

  • const字段只能在字段的声明语句中初始化,而readonly字段可以在下列任意位置设置它的值

       1.字段声明语句,类似于const 2.类的任何构造函数,如果是static字段,初始化必须在静态构造函数中完成       

  • const字段的值必须在编译时决定,而readonly字段的值可以在运行时决定。这种自由性允许你在不同环境或不同构造函数设置不同的值。
  • const的行为总是静态的,而对于readonly字段可以使静态字段也可以是实例字段。                                   

 6.索引器和属性

索引器和属性在很多方面是相似的:

  • 和属性一样,索引器不用分配内存来存储
  • 索引器和属性都主要被用来访问其他数据成员,他们与这些成员关联,并为他们提供获取和设置访问。
  • 属性通常表示单个数据成员,索引器通常表示多个数据成员。
  • 和属性一样,索引器可以只有一个访问器,也可以两个都有
  • 索引器总是实例成员,因此不能被声明为static
  • 和属性一样,实现get和set的访问器代码不一定要关联某个字段或属性,这段代码可以做任何事情也可以什么都不做,只要get访问器返回某个指定类型的值即可。

索引器的声明:

ReturnType this [Type param1,...]
{
    get{...}
    set{...}
}

看个具体例子:

 public class Indexer
    {
        private int Temp1;
        private int Temp2;
        public int this[int index]
        {
            get
            {
                return (0==index) ? Temp1 : Temp2;
            }
            set
            {
                if(0==index)
                    Temp1 = value;
                else
                    Temp2 = value;
            }
        }

        public static void Test()
        {
            Indexer indexer = new Indexer();
            indexer.Display();
            indexer[0] = 1;
            indexer[1]=2;
            indexer.Display();
        }
        public void Display()
        {
            Console.WriteLine($"temp1: {Temp1}  temp2: {Temp2}");
        }
    }

7.override&new

 7.1 new复写

        使用new关键字可以在派生类中屏蔽基本函数(如果派生类不给new关键字,会给出警告):

public class DeriveStrategy
    {
        public static void Test()
        {
            MyDerivedClass d = new();
            d.Print();
            var b=(MyBaseClass)d;
            b.Print();
        }
    }

    public class MyBaseClass
    {
        public void Print()
            => Console.WriteLine("This is base class!");
    }
    public class MyDerivedClass:MyBaseClass
    {
        public int val;
        public void Print()
            => Console.WriteLine("This is derive class!");
    }

-----------------------
//ans
This is derive class!
This is base class!

从下图就可以看出:

 从上图可以看出,派生类可以看到完整的对象,基类不能看到派生类成员。

7.2 override覆写

         关于override的点有:

  • 派生方法与基类方法有相同的签名和返回类型
  • 基类的方法使用virtual标注,派生类的方法使用override标注
  • 当基类调用print方法时,方法调用被传递到派生类并执行

下图阐释了这一点:

看个例子:

public class A
    {
        public virtual void Print() => Console.WriteLine("This is class A");
    }
    public class B : A
    {
        public override void Print() => Console.WriteLine("This is class B");
    }


 public class DeriveStrategy
    {
        public static void Test()
        {
            MyDerivedClass d = new();
            d.Print();
            var b=(MyBaseClass)d;
            b.Print();
        }
        public static void TestOverride()
        {
            B b=new B();
            b.Print();
            var a=(A)b;
            a.Print();  
        }
    }
//运行结果
This is class B
This is class B

 很显然使用override覆写和new覆盖是不一样的:当一个对象基类的引用调用一个被覆写的方法时,方法的调用被沿派生层次上溯执行,一直到标记override的方法的最高派生。

8.internal

 标记为public的类可以被系统内任何程序集中的代码访问,要使一个类对其它程序集可见,使用public访问修饰符。

标记为internal的类只能被它自己的程序集的类看到,这是默认的访问级别,所以除非在类中显示指定修饰符public,否则程序集外部代码不能访问该类。

9.Switch的技巧

switch在C#7.0后可以测试任何类型,每个分支标签后面跟着一个模式表达式,该模式表达式将与测试表达式进行比较。下面看一个测试类型的例子:

 public static void Test()
        {
            var shapes=new List<Shape>();
            shapes.Add(new Circle() { Radius=7 });
            shapes.Add(new Square() { Side=4});
            shapes.Add(new Triangle() { Height=5});
            var nullShape = (Square)null;
            shapes.Add(nullShape);
            foreach(var shape in shapes)
            {
                switch(shape)
                {
                    case Circle circle:
                        Console.WriteLine("This is a circle");
                        break;
                    case Square square:
                        Console.WriteLine("This is a square");
                        break;
                    case Triangle triangle:
                        Console.WriteLine("This is a triangle");
                        break;
                    case null:
                        Console.WriteLine("This can be circle square triangle");
                        break ;
                    default:
                        throw new ArgumentException(message: "shape is not a recognise shape", paramName: nameof(shape));

                }
            }
        }
    }
    public abstract class Shape { }
    public class Square : Shape 
    { 
        public double Side { get; set; }
    }
    public class Circle:Shape
    {
        public double Radius { get; set; }
    }
    public class Triangle:Shape
    {
        public double Height { get; set; }
    }

每个Switch等价于 :if(shape is circle)

10.多维数组例子

 internal class ArrayCombo
    {
        public static void Test()
        {
            int[][,] arr = new int[3][,];
            for (int i = 0; i < arr.GetLength(0); i++)
            {
                var temp = new int[2, 3];
                for(int j = 0; j < 2;j++)
                {
                    for(int k = 0; k < 3;k++)
                    {
                        temp[j, k] = i + j + k;
                    }
                }
                arr[i] = temp;
            }
            for(int i =0;i<arr.GetLength(0);i++)
            {
                for(int j=0;j<arr[i].GetLength(0);j++)
                {
                    for(int k=0;k<arr[i].GetLength(1);k++)
                    {
                        Console.WriteLine($"[{i}][{j},{k}]={arr[i][j, k]}");
                    }
                    Console.WriteLine();
                }
                Console.WriteLine();
            }

        }
    }

矩形数组和交错数组的结构区别非常大,例如同样是3*3的数组,在内存的结构如下:

  •  两个数组都保存了9个证书,但是它们的结构却很不相同
  • 矩形数组只有单个数组对象,而交错数组有4个数组对象

在CIL中,一维数组有特定的指令用于性能优化。矩形数组没有这些指令,并且不在相同级别进行优化。因此,有时使用一维数组的交错数组比矩形数组更有效率。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值