c#快速入门~在java基础上,知道C#和JAVA 的不同即可


☺ 观看下文前提:如果你的主语言是java,现在想再学一门新语言C#,下文是在java基础上,对比和java的不同,快速上手C#,当然不是说学C#的前提是需要java,而是下文是从主语言是java的情况下,学习C#入门到进阶。


C# 学习参考文档和开发工具



C# 概述

C# 概述

  • C#(发音为 "C sharp")是一种新式编程语言,不仅面向对象,还类型安全。
  • 它是由 微软 (Microsoft)开发的。
  • C# 编程是基于 C 和 C++ 编程语言的, 源于 C 语言系列
  • 虽然 C# 的构想十分接近于传统高级语言 C 和 C++,是一门面向对象的编程语言, 但是它与 Java 非常相似
  • C# 程序在 .NET 上运行
  • C# 文件的后缀为 .cs

C# 与.Net的关系

  • C# 是由 Anders Hejlsberg 和他的团队在 .Net 框架开发期间开发的
  • C# 程序在 .NET 上运行,而 .NET 是名为公共语言运行时 (CLR) 的虚执行系统和一组类库。 CLR 是 Microsoft 对公共语言基础结构 (CLI) 国际标准的实现。
  • .NET是微软公司下的一个开发平台,.NET核心就是.NET Framwork(.NET框架)是.NET程序开发和运行的环境。
  • 用 C# 编写的源代码被编译成符合 CLI 规范的中间语言 (IL)。 IL 代码和资源(如位图和字符串)存储在扩展名通常为 .dll 的程序集中。 程序集包含一个介绍程序集的类型、版本和区域性的清单。
  • 通过 C# 生成的 IL 代码可以与通过 .NET 版本的 F#、Visual Basic、C++ 生成的代码进行交互。

C# 特性

简单,现代,面向对象类型安全版本控制兼容,灵活

  • 简单:虽然 C# 的构想十分接近于传统高级语言 C 和 C++,是一门面向对象的编程语言, 但是它与 Java 非常相似 。所以它容易上手

  • 类型安全:C# 允许动态分配轻型结构的对象和内嵌存储。 C# 支持泛型方法和类型,因此增强了类型安全性和性能。

  • 兼容: C# 有统一类型系统。 所有 C# 类型(包括 intdouble 等基元类型)均继承自一个根 object 类型。 所有类型共用一组通用运算。 任何类型的值都可以一致地进行存储、传输和处理。 此外,C# 还支持用户定义的引用类型值类型

  • 版本控制:C# 强调版本控制,以确保程序和库以兼容方式随时间推移而变化。 C# 设计中受版本控制加强直接影响的方面包括:单独的 virtualoverride 修饰符,关于方法重载决策的规则,以及对显式接口成员声明的支持。


C# 流行原因:

  • 现代的、通用的编程语言。
  • 面向对象。
  • 面向组件。
  • 容易学习。
  • 结构化语言。
  • 它产生高效率的程序。
  • 它可以在多种计算机平台上编译。
  • .Net 框架的一部分。

C# 一些重要的功能:

  • 布尔条件(Boolean Conditions)
  • 自动垃圾回收(Automatic Garbage Collection)
  • 标准库(Standard Library)
  • 组件版本(Assembly Versioning)
  • 属性(Properties)和事件(Events)
  • 委托(Delegates)和事件管理(Events Management)
  • 易于使用的泛型(Generics)
  • 索引器(Indexers)
  • 条件编译(Conditional Compilation)
  • 简单的多线程(Multithreading)
  • LINQ 和 Lambda 表达式
  • 集成 Windows




c# 快速入门~C# 和 java 的不同

1、入门demo的hello world

using System;
namespace HelloWorldApplication
{
   class HelloWorld
   {
      static void Main(string[] args)
      {
         /* 我的第一个 C# 程序*/
         Console.WriteLine("Hello World");
         Console.ReadKey();
      }
   }
}
  • using 关键字用于在程序中包含 System 命名空间。 一个程序一般有多个 using 语句。

  • 命名空间声明(Namespace declaration)

  • 最后一行 Console.ReadKey(); 是针对 VS.NET 用户的。这使得程序会等待一个按键的动作,防止程序从 Visual Studio .NET 启动时屏幕会快速运行并关闭


☺ 如果是通过 Visual Studio 写的c# 项目:

目录结构:

  • 解决方案:相当于项目的工程空间,工程空间是可以有多个项目的

  • .vs文件: 是和项目配置相关的

  • 后缀 .sln 文件:是解决方案文件,通过该文件可以打开解决方案

  • 项目中后缀是 .cs 文件:就是咱用 c# 写的代码


☺ 对于hello world 程序

(1) using

  • 在任何 C# 程序中的第一条语句都是:using System;

(2) namespace


2、注释

  • 和java 是一样的~
  • 补充:关键词 #region 和 #endregion 用来管理注释


3、变量命名规范

  • 除了数字、字母,还可以是@

4、C# 的变量类型:还包含了指针类型

在 C# 中,变量分为以下几种类型:

  • 值类型(Value types)
  • 引用类型(Reference types)
  • 指针类型(Pointer types)

引用类型:

内置的引用类型有:objectdynamicstring


■ 动态(Dynamic)类型

您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。

声明动态类型的语法:dynamic <variable_name> = value;

  • 举例子:dynamic d = 20;

☺ 字符串(String)类型:还有@ 修饰,@有其相对的意义

  • 字符串(String)类型的值可以通过两种形式进行分配:引号和 @引号。
  • 举例子1:使用引号的
String str = "runoob.com";
  • 举例子2:使用@,意义:

    (1)取消 \ 在字符串中的转义作用,使其单纯的表示为一个‘\’。
    (2)将字符串按照编辑的原格式输出。

string str = @"C:\Windows"; //相当于string str = "C:\\Windows";

//作用2:使其按照编辑的原格式输出
Console.WriteLine(@"你好啊!
我的朋友!");
Console.ReadKey();

//输出结果为:
你好啊!
我的朋友!   

■ 指针类型(Pointer types)

  • C# 中的指针与 C 或 C++ 中的指针有相同的功能。语法:type* identifier;

  • 举例子:

char* cptr;
int* iptr;

■ 用户自定义引用类型有:class、interface 或 delegate。


5、定义常量,使用关键词 const


6、占位符的

  • Console.WriteLine的后半部的参数变量的顺序就是对应 {0}、{1}、{2}、{3}...

  • 举例子:

int n1 = 10;
int n2 = 20;
int n3 = 30;
Console.WriteLine("参数结果是:{0},{1},{2}", n1, n2, n3);
Console.ReadKey();

//输出结果为:
参数结果是:10,20,30

7、C# 的运算符,和java 不太一样的是它还有其他运算符

  • 对于算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符 和java是一样的

  • 其他运算符:

运算符 描述 实例
is 判断对象是否为某一类型。 If( Ford is Car) // 检查 Ford 是否是 Car 类的一个对象。
as 强制转换,即使转换失败也不会抛出异常。 Object obj = new StringReader("Hello"); StringReader r = obj as StringReader;

8、C# 的访问权限-private、protected、internal、protected internal、public

  • 和java有点点不一样的是,default 权限在C# 被叫为internal,并且c# 多了一个访问权限为protected internal

  • 其他的都是差不多一样的: private、protected、internal、protected internal、public


☺ 9、C# 方法的参数传递,ref关键词的使用,实现参数作为引用类型,out关键字的使用,实现参数作为输出类型

  • c# 方法的定义,调用和java 是一模一样的
  • 参数的传递和 java 也是一模一样的,有三种情况:值参数、引用参数、输出参数

☺ 输出参数的作用:方法没有返回值时,而需要从该方法中返回结果的时候,需要使用输出参数


ref 类型的使用,实现参数作为引用类型:

using System;
namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void swap(ref int x, ref int y)
      {
         int temp;

         temp = x; /* 保存 x 的值 */
         x = y;    /* 把 y 赋值给 x */
         y = temp; /* 把 temp 赋值给 y */
       }
   
      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* 局部变量定义 */
         int a = 100;
         int b = 200;

         Console.WriteLine("在交换之前,a 的值: {0}", a);
         Console.WriteLine("在交换之前,b 的值: {0}", b);

         /* 调用函数来交换值 */
         n.swap(ref a, ref b);//-------这里使用了关键词ref,实现了参数作用引用类型--------

         Console.WriteLine("在交换之后,a 的值: {0}", a);
         Console.WriteLine("在交换之后,b 的值: {0}", b);
 
         Console.ReadLine();

      }
   }
}
  • 结果:

在交换之前,a 的值:100
在交换之前,b 的值:200
在交换之后,a 的值:100
在交换之后,b 的值:200


out关键字的使用,实现参数作为输出类型

using System;

namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void getValue(out int x )
      {
         int temp = 5;
         x = temp;
      }
   
      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* 局部变量定义 */
         int a = 100;
         
         Console.WriteLine("在方法调用之前,a 的值: {0}", a);
         
         /* 调用函数来获取值 */
         n.getValue(out a);//-------这里使用了关键词out,实现了实现参数作为输出类型--------

         Console.WriteLine("在方法调用之后,a 的值: {0}", a);
         Console.ReadLine();

      }
   }
}
  • 结果:

在方法调用之前,a 的值: 100
在方法调用之后,a 的值: 5



10、不定长参数-关键词 params+数组

public 返回类型 方法名称( params 类型名称[] 数组名称 )
  • 举例子:
using System;

namespace ArrayApplication
{
   class ParamArray
   {
      public int AddElements(params int[] arr)  //-----------不定长参数----------
      {
         int sum = 0;
         foreach (int i in arr)
         {
            sum += i;
         }
         return sum;
      }
   }
  
    
   class TestClass
   {
      static void Main(string[] args)
      {
         ParamArray app = new ParamArray();
         int sum = app.AddElements(512, 720, 250, 567, 889);
         Console.WriteLine("总和是: {0}", sum);
         Console.ReadKey();
      }
   }
}



11、可空类型--单问号 ? 与 双问号 ??

■ 可空类型(Nullable Type)表示在值类型的正常取值范围内再加上一个null值.

  • 举例子:
int i; //默认值0
int? ii; //默认值null,可空的int

//-----------------------
int? i = 3; // 相当于Nullable<int> i = new Nullable<int>(3);

■ 一个?和两个?的区别:

  • 一个问号:用于定义值变量是可空的值变量。
  • 两个问号:用于判断一个可空的变量当前值是否为null,若是null,则返回指定的值。
a = b ?? c //b是一个可空的值变量,如果 b 为 null,则 a = c,如果 b 不为 null,则 a = b
    
double? num1 = null;
double num3 = num1 ?? 5.34;      // num1 如果为空值则返回 5.34



12、二维数组的写法有点不同

	   static void Main(string[] args)
        {
            /* 一个带有 5 行 2 列的数组 */
            int[,] a = new int[5, 2] {{0,0}, {1,2}, {2,4}, {3,6}, {4,8} };

            int i, j;
            /* 输出数组中每个元素的值 */
            for (i = 0; i < 5; i++)
            {
                for (j = 0; j < 2; j++)
                {
                    Console.WriteLine("a[{0},{1}] = {2}", i, j, a[i,j]);
                }
            }
           Console.ReadKey();
        }

■ Array 类的常用方法:

Array 类的方法

  • Array 类的常用属性:LengthLongLength
序号 方法 & 描述
1 Clear 根据元素的类型,设置数组中某个范围的元素为零、为 false 或者为 null。
2 Copy(Array, Array, Int32) 从数组的第一个元素开始复制某个范围的元素到另一个数组的第一个元素位置。长度由一个 32 位整数指定。
3 CopyTo(Array, Int32) 从当前的一维数组中复制所有的元素到一个指定的一维数组的指定索引位置。索引由一个 32 位整数指定。
4 GetLength 获取一个 32 位整数,该值表示指定维度的数组中的元素总数。
5 GetLongLength 获取一个 64 位整数,该值表示指定维度的数组中的元素总数。
6 GetLowerBound 获取数组中指定维度的下界。
7 GetType 获取当前实例的类型。从对象(Object)继承。
8 GetUpperBound 获取数组中指定维度的上界。
9 GetValue(Int32) 获取一维数组中指定位置的值。索引由一个 32 位整数指定。
10 IndexOf(Array, Object) 搜索指定的对象,返回整个一维数组中第一次出现的索引。
11 Reverse(Array) 逆转整个一维数组中元素的顺序。
12 SetValue(Object, Int32) 给一维数组中指定位置的元素设置值。索引由一个 32 位整数指定。
13 Sort(Array) 使用数组的每个元素的 IComparable 实现来排序整个一维数组中的元素。
14 ToString 返回一个表示当前对象的字符串。从对象(Object)继承。



13、c# 中还有结构体,在 C# 中的结构与传统的 C 或 C++ 中的结构不同。

■ 在 C# 中的结构与传统的 C 或 C++ 中的结构不同:

  • 结构可带有方法、字段、索引、属性、运算符方法和事件。
  • 结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义无参构造函数。无参构造函数(默认)是自动定义的,且不能被改变。
  • 与类不同,结构不能继承其他的结构或类。
  • 结构不能作为其他结构或类的基础结构。
  • 结构可实现一个或多个接口。
  • 结构成员不能指定为 abstract、virtual 或 protected。
  • 当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
  • 如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。

■ 类 vs 结构:

  • 类是引用类型,结构是值类型。

  • 结构不支持继承。

  • 结构不能声明默认的构造函数。


■ 举例子:

//声明 Book 结构
struct Books
{
   public string title;
   public string author;
   public string subject;
   public int book_id;
};  
//使用结构体
public class testStructure
{
   public static void Main(string[] args)
   {
      Books Book1;        /* 声明 Book1,类型为 Books */
      /* book 1 详述 */
      Book1.title = "C Programming";
      Book1.author = "Nuha Ali";
      Book1.subject = "C Programming Tutorial";
      Book1.book_id = 6495407;
      /* 打印 Book1 信息 */
      Console.WriteLine( "Book 1 title : {0}", Book1.title);
      Console.WriteLine("Book 1 author : {0}", Book1.author);
      Console.WriteLine("Book 1 subject : {0}", Book1.subject);
      Console.WriteLine("Book 1 book_id :{0}", Book1.book_id);
   }
}

■ 类 vs 结构的使用场景:

结构和类的区别:

  • 1、结构是值类型,它在栈中分配空间;而类是引用类型,它在堆中分配空间,栈中保存的只是引用。
  • 2、结构类型直接存储成员数据,让其他类的数据位于堆中,位于栈中的变量保存的是指向堆中数据对象的引用。

由于结构是值类型,并且直接存储数据,因此在一个对象的主要成员为数据且数据量不大的情况下,使用结构会带来更好的性能。

因为结构是值类型,因此在为结构分配内存,或者当结构超出了作用域被删除时,性能会非常好,因为他们将内联或者保存在堆栈中。

▷ 结构和类的适用场合分析:

  • 1、当堆栈的空间很有限,且有大量的逻辑对象时,创建类要比创建结构好一些;
  • 2、对于点、矩形和颜色这样的 轻量对象 ,假如要声明一个含有许多个颜色对象的数组,则CLR需要为每个对象分配内存,在这种情况下,使用结构的成本较低;
  • 3、在表现抽象和多级别的对象层次时,类是最好的选择,因为结构不支持继承。
  • 4、大多数情况下,目标类型只是含有一些数据,或者以数据为主。



14、c# 的类和java是一模一样的,就是多了个析构函数

  • c#中的类,类的定义-成员变量、成员方法,类的构造函数,类的实例化、调用类的成员变量、方法,都是和java一模一样的!

  • 析构函数:

    • 析构函数的名称是在类的名称前加上一个波浪形(~)作为前缀,它不返回值,也不带任何参数。
    • 析构函数用于在结束程序(比如关闭文件、释放内存等)之前释放资源 。析构函数不能继承或重载。
  • 举例子:

using System;
namespace LineApplication
{
   class Line
   {
      private double length;   // 线条的长度
      public Line()  // 构造函数
      {
         Console.WriteLine("对象已创建");
      }
      ~Line() //析构函数
      {
         Console.WriteLine("对象已删除");
      }

      public void setLength( double len )
      {
         length = len;
      }
      public double getLength()
      {
         return length;
      }

      static void Main(string[] args)
      {
         Line line = new Line();
         // 设置线条长度
         line.setLength(6.0);
         Console.WriteLine("线条的长度: {0}", line.getLength());          
      }
   }
}
  • 结果:

对象已创建
线条的长度: 6
对象已删除




15、C# 的继承、实现,还有抽象类,重写抽象类的方法的写法以及 abstract 和 virtual 的区别(最后总结一下,在C#中子类和父类的同名方法的关系)

■ 继承语法:

<访问修饰符> class <基类>
{
 ...
}
class <派生类> : <基类>
{
 ...
}
  • 子类继承父类,语法:
 class 子类: 父类
  • 子类继承父类的构造方法,语法:
public 子类(参数列表): base(参数列表){}
  • 子类调用父类中的普通的方法(非构造方法),语法:
子类声明的方法{
	base.父类声明的方法(参数列表);
}

■ 举例子:

using System;
namespace InheritanceApplication
{
   class Shape
   {
      public void setWidth(int w)
      {
         width = w;
      }
      public void setHeight(int h)
      {
         height = h;
      }
      protected int width;
      protected int height;
   }

   // 派生类
   class Rectangle: Shape
   {
      public int getArea()
      {
         return (width * height);
      }
   }
   
}

■ 接口interface实现的语法:

//声明为接口,使用的关键词还是 interface 
   // 接口 PaintCost
   public interface PaintCost
   {
      int getCost(int area);//接口中的方法,是没有任何修饰符
   }

//子类实现接口,是使用冒号,代替java的implements
 // 派生类
   class Rectangle : PaintCost
   {
      public int getCost(int area)
      {
         return area * 70;
      }
   }

▷ 接口:接口是可以有属性看,但是不能有字段的!

// 接口 PaintCost
public interface PaintCost
{
 int Cost{set; get;}//接口可以有属性(属性是特殊的方法,属性的特点:是通过set设置值,通过get获取值)
 //public int cost2; 接口是不能有字段的
}
  • 这一块属性的写法,属于简写,详细的介绍,在后边~

▷ 子类实现接口:有两种方式,常用的是隐式(子类实现接口中声明的方法时用 public),显示就是方法前有接口名:

  • 接口interface实现的语法的例子就是隐式实现
// 显示实现,举例子:
   
// 接口 PaintCost
public interface PaintCost
{
      int getCost(int area);//接口中的方法,是没有任何修饰符
}

 // 派生类
class Rectangle: PaintCost
{
      int PaintCost.getCost(int area)
      {
         return area * 70;
      }
}

■ 子类继承抽象类,重写抽象类的方法的写法:重写abstract修饰的方法,要加override

abstract class Shape
{
   abstract public int area();
}

class Rectangle:  Shape, PaintCost 
{
      public override int area ()
      {
         Console.WriteLine("Rectangle 类的面积:");
         return 100;
      }
}

■ abstract 和 virtual 的区别:

▷ 区别点:

  • abstract 声明的方法-抽象方法必须存在于抽象类中,抽象类中的抽象方法在声明的时候是不可以有实现体的。对于子类继承了抽象类,就必须重写人家所有的抽象方法!

  • 而 virtual 声明的方法,没有要求一定需要存在什么类中,可以存在抽象父类、普通父类中,并且要求声明为 virtual 方法的同时,需要有实现体!对于子类继承了父类,可以重写或者不重写 父类 virtual 声明的方法。

  • virtual和abstract 它们有一个共同点:如果用来修饰方法,前面必须添加public,要不然就会出现编译错误:虚拟方法或抽象方法是不能私有的。

▷ 举例子:

//abstract
abstract class BaseTest1
{
    public abstract void fun();
}
class DeriveTest1: BaseTest2
{
    public override void fun() { }//对于子类继承了抽象类,就必须重写人家所有的抽象方法!

}


//virtual
class BaseTest2
{
   public virtual void fun() { }//必须有实现
}
class DeriveTest2: BaseTest1
{
    //public override void fun() { } //对于子类继承了父类,可以重写或者不重写 父类 virtual 声明的方法。
}

☺ 最后总结,在C#中子类和父类的同名方法的关系,C# 细分了,重写和覆盖是不同的,这里和java 不一样,在java中重写就是覆盖,覆盖就是重写!

▷重写使用的频率比较高,实现多态;覆盖用的频率比较低,用于对以前无法修改的类进行继承的时候。

☺ (1) 重写(关键词是override)

  • 重写的意义:实现多态性多态的意思是,父类引用指向子类实例的时候。当子类重写了父类的这个方法。那么父类的引用调用是子类的方法。
//代码中的写法,多态的写法:
父亲 p = new 子类(); 
//接着调用同名方法
p.sameFunction();
//这时候,如果子类中和父类的同名方法,没有使用关键词override 进行修饰,那么实际上调用的是父类的方法


-------------------------------------------------- 举例子-----------------------------------------------
using System;
namespace TestApplication
{
    public class Shape
    {
        public void ordinaryFunction()
        {
            Console.WriteLine(" Shape中的ordinaryFunction");
        }
    }

    public class Rectangle: Shape
    {
        public new void ordinaryFunction()//子类中和父类的同名方法,没有使用关键词override 进行修饰,即没有重写的作用
        {
            Console.WriteLine(" Rectangle中的ordinaryFunction");
        }
    }

    class RectangleTester
    {
        static void Main(string[] args)
        {
            Shape s = new Rectangle();//Rectangle 没有重写的情况下,调用的是父类的方法
            s.ordinaryFunction();
            Console.ReadKey();
        }
    }
}   

(2) 覆盖(关键词是new)

  • 覆盖的意义:其实就隐藏,因为子类中的方法【返回值、参数列表情况都和父类一模一样,这就不是重构了,本质上,子类和父类的这个同名方法是两个不同的方法】,覆盖的作用就是直接隐藏掉父类的方法,直接调用子类方法。
public class Shape
{
    public void ordinaryFunction()
    {
          Console.WriteLine(" Shape中的ordinaryFunction");
    }
}

public class Rectangle : Shape
{
     public new void ordinaryFunction() //重写基类的同名方法要加上new,否则会有警告
     {
          Console.WriteLine(" Rectangle中的ordinaryFunction");
     }
}



☺ 16、总结 c#的重要的关键词 new、virtual、override、abstract、interface

参考:作者-北盟网校,B站视频《https://www.bilibili.com/video/BV1xP4y1y783/》,下面的文字内容就是作者-北盟网校整个视频所讲的重点了~

■ new关键字,目的是为了隐藏父类同名同参数的方法。不写也可以,但会有警告,建议写一下。

  • 因为默认就会覆盖子类的方法。覆盖的意思是子类可以调用父类的公开和受保护的方法的。但是万一子类有一个同名同参数的方法,这个时候就不再调用父类继承过来的方法,而是调用自己的方法。

■ virtual方法和普通方法一样,但是加上 virtual 后就允许子类重写 override。

■ override目的就是为了多态。

  • 多态是父类引用指向子类对象,调用的是子类的方法体。如果没有 override 那么还是得用父类的方法。
  • 多态的实现可以使用 virtual+override 或者 abstract+override 或者 接口+实现(没有 override) 或者 override+override
    • override 的源头方法 只有两种 virtual和 abstract

■ abstract方法是抽象方法,没有方法体,抽象方法必须存在抽象类里。

  • 抽象类可以有非抽象方法 和属性 字段等。抽象类是不能 new 的。它的抽象方法没有方法体的。

■ interface接口可以有属性。但是不能有字段。方法也是没有修饰符的。没有方法体。

  • 实现了接口的类的方法不需要用 override 关键字的。



17、C# 命名空间 namespace

  • 和java 的包名作用都是一样了,都是为了实现代码的复用。
  • 我们知道,重用性(reusebility)是软件工程中一个非常重要的目标。重用,不仅仅指自己所写的软件(代码、组件等等)可以被重复利用;更广义的重用是指不同的人,不同的团队,不同的公司之间可以互相利用别人的成果。另外,对于大型软件,往往是由多个团队共同开发的,这些团队有可能分布于不同的城市、地区、甚至国家。由于这些原因,名字管理成为一个非常重要的因素

C++和C# 提供了namespace的概念来支持这种方式。你可以在全局的空间内指定自己的namespace,然后还可以在某个namespace内制定更小范围的namespace。



18、预处理

C# 预处理器指令列表

预处理器指令 描述
#define 它用于定义一系列成为符号的字符。
#undef 它用于取消定义符号。
#if 它用于测试符号是否为真。
#else 它用于创建复合条件指令,与 #if 一起使用。
#elif 它用于创建复合条件指令。
#endif 指定一个条件指令的结束。
#line 它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。
#error 它允许从代码的指定位置生成一个错误。
#warning 它允许从代码的指定位置生成一级警告。
#region 它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。
#endregion 它标识着 #region 块的结束。

预处理器指令指导编译器在实际编译开始之前对信息进行预处理。

所有的预处理器指令都是以 # 开始。且在一行上,只有空白字符可以出现在预处理器指令之前。预处理器指令不是语句,所以它们不以分号(;)结束。

C# 编译器没有一个单独的预处理器,但是,指令被处理时就像是有一个单独的预处理器一样。在 C# 中,预处理器指令用于在条件编译中起作用。与 C 和 C++ 不同的是,它们不是用来创建宏。一个预处理器指令必须是该行上的唯一指令。



19、C# 正则表达式



20、异常

  • 异常处理的关键词和java是一样的,都是那几个关键词:trycatchfinallythrow
  • 不过,异常的根类和继承关系有点不同,自定义异常的时候,要注意一下是应用异常,还是系统异常,然后再继承该异常类:

C# 中的异常类主要是直接或间接地派生于 System.Exception 类。System.ApplicationExceptionSystem.SystemException 类是派生于 System.Exception 类的异常类。

  • System.ApplicationException 类支持由应用程序生成的异常。所以程序员定义的异常都应派生自该类。

  • System.SystemException 类是所有预定义的系统异常的基类。



21、反射结合了特性一起使用

(1) 优缺点:

优点:

  • 1、反射提高了程序的灵活性和扩展性。
  • 2、降低耦合性,提高自适应能力。
  • 3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。

缺点:

  • 1、性能问题:使用反射基本上是一种解释操作, 用于字段和方法接入时要远慢于直接代码 。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
  • 2、使用反射会模糊程序内部逻辑程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术 ,因而会带来维护的问题,反射代码比相应的直接代码更复杂。

(2) 特性

预定义特性(Attribute)

Net 框架提供了三种预定义特性:

  • AttributeUsage
  • Conditional
  • Obsolete

AttributeUsage

▪ 预定义特性 AttributeUsage 描述了如何使用一个自定义特性类。它规定了特性可应用到的项目的类型。

▪ 规定该特性的语法如下:

[AttributeUsage(
   validon,
   AllowMultiple=allowmultiple,
   Inherited=inherited
)]
  • 参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All
  • 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。
  • 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property, 
AllowMultiple = true)]



22、访问器

get 访问器、set 访问器

// 声明类型为 string 的 Name 属性
public string Name
{
   get
   {
     return name;
   }
   set
   {
     name = value;
   }
}


// 简写为:
public string Name {get; set;}

  • 举例子:
using System;
namespace runoob
{
   public abstract class Person
   {
      public abstract string Name{get; set;}
      public abstract int Age{get; set;}
   }
   class Student : Person
   {
      private string name = "N.A";
      private int age = 0;
      // 声明类型为 string 的 Name 属性
      public override string Name
      {
         get
         {
            return name;
         }
         set
         {
            name = value;
         }
      }
      // 声明类型为 int 的 Age 属性
      public override int Age
      {
         get
         {
            return age;
         }
         set
         {
            age = value;
         }
      }
      public override string ToString()
      {
         return Name = " + Name + ", Age = " + Age;
      }
   }
}



23、委托,关键词 delegate

其实底层就是使用的是反射!

■ 委托:类似于 C 或 C++ 中函数的指针!即用变量调方法。好处:使方法的使用变得更加灵活。

  • 某个方法自己不调用,而将方法自己委托给另一个变量,由这个变量执行这个方法,执行结果和这个方法自己执行是一样的

■ 委托在实际使用中的例子:

  • 自定义排序
  • 窗体传值
  • 多线程操作

■ 举例子:

  • 声明为委托类型,语法:
public|internal delegate 返回值 委托名(参数列表);
  • 对于声明为委托类型的方法,是需要使用new 关键词进行实例化创建的,然后使用变量进行引用,最后通过该变量执行
//声明为委托类型
delegate int NumberChanger(int n);
--------------------------------------------------------------------
  public static int AddNum(int p)
  {
      num += p;
      return num;
  }
--------------------------------------------------------------------
// 使用new 创建委托实例 或者直接使用变量指向方法
NumberChanger nc1 = new NumberChanger(AddNum);//相当于 NumberChanger nc1 = AddNum;
// 使用委托对象调用方法,执行AddNum 方法
nc1(25);

■ 委托的多播,简单理解,理解成java中的链式调用即可

  • 举例子:
using System;

delegate int NumberChanger(int n);
namespace DelegateAppl
{
   class TestDelegate
   {
      static int num = 10;
      public static int AddNum(int p)
      {
         num += p;
         return num;
      }

      public static int MultNum(int q)
      {
         num *= q;
         return num;
      }
      public static int getNum()
      {
         return num;
      }

      static void Main(string[] args)
      {
         // 创建委托实例
         NumberChanger nc;
         NumberChanger nc1 = new NumberChanger(AddNum);
         NumberChanger nc2 = new NumberChanger(MultNum);
         nc = nc1;
         nc += nc2;
         // 调用多播
         nc(5);
         Console.WriteLine("Value of Num: {0}", getNum());
         Console.ReadKey();
      }
   }
}
  • 结果:

Value of Num: 75



24、事件【设计模式-发布订阅模式】

(1) 为什么需要事件?

  • 原因1:

看看前面的委托的声明的语法,声明为委托类型的时候,修饰符是 public|internal

而有时候,程序为了安全考虑,需要私有化,即封装起来,不给外界随意调用,随意修改!

  • 原因2:

委托链可以累加方法,+= 误用了 = 那么只会执行最后一个=的方法,不安全,有被覆盖的隐患

事件实际上是一个私有委托变量,对外界开放了一个向委托变量增加方法绑定的方法,和开放了一个减少委托变量身上绑定的方法


☺ 官网对事件的概述:

事件是一种特殊的多播委托,仅可以从声明事件的类(或派生类)或结构发布服务器类中对其进行调用

如果其他类或结构订阅该事件,则在发布服务器类引发该事件时,将调用其事件处理程序方法

  • 例子1:发布服务器类

event 关键字用于声明发布服务器类中的事件。

//关于如何声明和引发使用 [EventHandler] 作为基础委托类型的事件

public class SampleEventArgs
{
    public SampleEventArgs(string text) { Text = text; }
    public string Text { get; } // readonly
}

public class Publisher
{
    // Declare the delegate (if using non-generic pattern).
    public delegate void SampleEventHandler(object sender, SampleEventArgs e);

    // Declare the event.
    public event SampleEventHandler SampleEvent;

    // Wrap the event in a protected virtual method
    // to enable derived classes to raise the event.
    protected virtual void RaiseSampleEvent()
    {
        // Raise the event in a thread-safe manner using the ?. operator.
        SampleEvent?.Invoke(this, new SampleEventArgs("Hello"));
    }
}
  • 例子2:就是在例子1的基础上,在发布服务器类中,添加了监听方法 onXX事件
using System;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Counter c = new Counter(new Random().Next(10));
            c.ThresholdReached += c_ThresholdReached;//真实的事件处理方法
            for(int i = 0; i < 10; i++)
            {
                Console.WriteLine("adding one");
                c.Add(1);
            }
        }
        /**
		* 真实的事件处理方法
 	    */
        static void c_ThresholdReached(object sender, ThresholdReachedEventArgs e)
        {
            Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold,  e.TimeReached);
            Environment.Exit(0);
        }
    }

   /**
   * 事件参数类
   * EventArgs: 是C# 内置的类,内部有一个委托属性 public delegate void EventHandler<TEventArgs>(object? sender, TEventArgs e);
   */
    public class ThresholdReachedEventArgs : EventArgs
    {
        public int Threshold { get; set; }
        public DateTime TimeReached { get; set; }
    }
    
    class Counter
    {
        //声明事件
        public event EventHandler<ThresholdReachedEventArgs> ThresholdReached;
        
        private int threshold;
        private int total;

        public Counter(int passedThreshold)
        {
            threshold = passedThreshold;
        }

        public void Add(int x)
        {
            total += x;
            if (total >= threshold)
            {
                ThresholdReachedEventArgs args = new ThresholdReachedEventArgs();
                args.Threshold = threshold;
                args.TimeReached = DateTime.Now;
                OnThresholdReached(args);//监听或通知事件
            }
        }
        /**
		*监听或通知事件
		*/
        protected virtual void OnThresholdReached(ThresholdReachedEventArgs e)
        {
            EventHandler<ThresholdReachedEventArgs> handler = ThresholdReached;
            if (handler != null)
            {
                handler(this, e);//事件处理程序
            }
        }
    }
}

(2) 事件的本质

以日志事件为例,日志事件写法:public event EventHandler LogHandler; 它的的本质如下:

private delegate EventHandler<LogHandlerArgs> LogHandler;
    
public event EventHandler<LogHandlerArgs> LogHandler
{
    add
    {
        LogHandler += value;//添加到事件的方法队列中
     }
     remove
     {
         LogHandler -= value;
     }
}

(3) 事件具有以下属性:

  • 事件通常用于表示用户操作,例如单击按钮或图形用户界面中的菜单选项

  • 发行者确定何时引发事件;订户确定对事件作出何种响应。

  • 一个事件可以有多个订户。 订户可以处理来自多个发行者的多个事件。

  • 没有订户的事件永远也不会引发。

  • 当事件具有多个订户时,引发该事件时会同步调用事件处理程序。

  • 在 .NET 类库中,事件基于 EventHandler 委托和 EventArgs 基类。



25、C# 不安全代码

当一个代码块使用 unsafe 修饰符标记时,C# 允许在函数中使用指针变量。不安全代码或非托管代码是指使用了指针变量的代码块。




如果本文对你有帮助的话记得给一乐点个赞哦,感谢!

posted @ 2023-04-10 01:56  一乐乐  阅读(745)  评论(8编辑  收藏  举报