文章目录
一. 一个简单的helloworld
using System;
class helloworld
{
//静态方法可以在不引用特定对象的情况下运行,所以main是程序入口点
static void Main()
{
Console.WriteLine("helloworld");
}
}
写完的程序保存在hello.cs中,可以在vs的命令行执行csc hello.cs
编译该程序,会生成一个hello.exe可执行程序集,然后.\hello.exe
就会输出helloworld。
二. 程序结构
C#中的关键组织结构包括:程序、命名空间、类型、成员和程序集。
- 程序声明类型,类型包含成员,并被整理到命名空间中
- 类型:包括类和接口
- 成员:包括字段、方法、属性和事件
- 编译完的C#程序会被打包到程序集中,扩展名为.exe或.dll
using System;
namespace Acme.Collections
{
public class Stack
{
Entry top;
public void push(object data)
{
top = new Entry(top,data);
}
public Object pop()
{
if (top == null) throw new InvalidOperationException();
object result = top.data;
top = top.next;
return result;
}
class Entry
{
public Entry next;
public object data;
public Entry(Entry next, object data)
{
this.next = next;
this.data = data;
}
}
}
}
使用csc /t:library acme.cs
编译成库(不含main入口点的代码),并生成acme.dll
程序集。
程序集是包含代码和元数据的自描述功能单元,所以不用在C#中引入#include指令和头文件,只需要在编译时引用特定的程序集。
下面程序使用了acme.dll中的Acme.Collections.Stack类
using System;
using Acme.Collections;
class Test
{
static void Main()
{
Stack s = new Stack();
s.push(1);
s.push(2);
s.push(3);
Console.WriteLine(s.pop());
Console.WriteLine(s.pop());
Console.WriteLine(s.pop());
}
}
test.cs和acme.dll可以用编译器的/r
选项来引用程序集:csc /r:acme.dll test.cs
,就可以生成test.exe可执行程序集,执行就可以输出3 2 1。
三. 类型和变量
C#的五个类别是用户可定义的类别:类类型、结构类型、接口类型、枚举类型和委托类型
- 类类型:包括数据成员(字段)和函数成员(方法、属性),支持单一继承和多形性
- 结构类型:包括数据成员和函数成员,值类型不需要堆分配,不支持用户指定的继承,均隐式继承object
- 接口类型:定义公共函数成员,实现接口的类或结构必须提供接口函数成员的实现
- 委托类型:对具有特定参数列表和返回类型的方法的使用。通过委托,可以将方法视为可分配给变量并可作为参数传递的实体,类似于函数指针,但比指针安全
- 枚举类型:枚举类型的值集与基础类型的值集相同。
- 类、结构、接口和委托都支持泛型,所以可以用其他类型参数化。
默认构造函数
所有值类型都隐式声明为默认构造函数的公共无参数实例构造函数,默认构造函数返回一个名为零初始化实例,该实例成为值类型的默认值:
- 简单类型
- 有无符号整型:默认值为0
- char:`\x0000`
- float:0.0f
- double:0.0d
- decimal:0.0m
- bool:false
- 枚举类型,默认值为0,转换为类型E
- 结构类型:默认值是通过将所有值类型的字段设置为其默认值,并将所有引用类型字段设置为null生成的值
- 可以为null的类型:默认值为HasValue属性为false且value属性未定义的实例。
值类型的默认构造函数使用new运算符进行调用,不会产生构造函数的调用
允许结构类型声明参数化实例构造函数,
每种C#类型都直接或间接地派生自object类类型,object是所有类型的最终基类。只需将值视为object,即可将引用类型的值视为对象,通过执行装箱或取消装箱操作,值类型的值被视为对象。值类型的值通过执行装箱和取消装箱的操作被视为对象。
using System;
class Test
{
static void Main()
{
int i = 123;
//int -> object
object o = i;
//object -> int
int j = (int)i;
}
}
在C#中,将零整数或浮点值转为布尔值,通过将整数或浮点值与零相比较来实现,或通过将对象引用显式比较为null来实现。
四. 表达式
- 可重载的一元运算符:+,-,!,~,++,–,true,false
- 可重载的二元运算符:+,-,*,/,%,&,|,^,<<,>>,==,!=,>,<,>=,<=
- 无法重载成员访问、方法调用、=、&&、||、??、?、=>、checked、unchecked、new、typeof、default、as和is
1. f(x)
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Caculator c = new Caculator();
//委托:不用去直接做这个事情,通过委托间接的调用一个方法
Action action = new Action(c.PrintHello);
action();
}
}
class Caculator
{
public void PrintHello()
{
Console.WriteLine("hello");
}
}
}
2. []
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
// 泛型:泛型类本身不是一个完整的类,需要和其他类型组合在一起称为一个完整的类。
Dictionary<string, Student> stuDic = new Dictionary<string, Student>();
for(int i = 0; i < 100; i++)
{
Student student = new Student();
student.Name = "S_" + i.ToString();
student.Score = 100 + i;
stuDic.Add(student.Name, student);
}
Student student6 = stuDic["S_6"];
Console.WriteLine(student6.Score);
}
}
class Student
{
public string Name;
public int Score;
}
}
3. typeof
typeof帮助我们查看一个类型的内部结构,类型的内部结构术语叫Metadata,里面包含这个类型的基本信息,比如:什么名字、属于的名词空间、父类、包含的方法和属性。等成员拿到这些信息后就可以根据这些信息决定自己的程序逻辑。
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Type t = typeof(int);
Console.WriteLine(t.FullName);
Console.WriteLine(t.Name);
Console.WriteLine(t.Namespace);
int c = t.GetMethods().Length;
foreach(var mi in t.GetMethods())
{
Console.WriteLine(mi.Name);
}
Console.WriteLine(c) ;
}
}
}
4. default
帮助获取一个类型的默认值
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
// 结构体类型
int x = default(int);
Console.WriteLine(x);
// 引用类型
Form form = default(Form);
Console.WriteLine(form==null);
//枚举类型
Level level = default(Level);
Console.WriteLine(level);
}
}
enum Level
{
Low,
Mid,
High
}
}
5. new
new可以调用示例的初始化器
Form form = new Form() { Text = "hello", FormBorderStyle = FormBorderStyle.SizableToolWindow};
form.ShowDialog();
//new和var组合:为匿名类型创建对象,并用隐式类型变量引用这个实例
var person = new{Name = "je", Age = 34};
Console.WriteLine(person.GetType().Name); // <>f__AnonymousType0`2
new还是关键字
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student();
stu.Report();
CsStudent cs = new CsStudent();
cs.Report();
}
}
class Student
{
public void Report()
{
Console.WriteLine("I'm a student.");
}
}
class CsStudent: Student
{
//子类把父类的方法隐藏了
new public void Report()
{
Console.WriteLine("I'm a Csstudent.");
}
}
}
6. checked和unchecked
C#默认采用unchecked
checked:编辑器要检查程序有没有溢出
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
uint x = uint.MaxValue;
Console.WriteLine(x);
string binStr = Convert.ToString(x, 2);
Console.WriteLine(binStr);
//法一
try
{
uint y = checked(x + 1);
Console.WriteLine(y);
}
catch (OverflowException ex)
{
Console.WriteLine("there is overflow!");
}
//法二
checked
{
try
{
uint y = x + 1;
Console.WriteLine(y);
}
catch (OverflowException ex)
{
Console.WriteLine("there is overflow!");
}
}
}
}
}
7. sizeof
sizeof只能用来获取结构体数据类型的实例在内存中的字节数,string和object不是结构体
在默认情况下,可以获取自定义数据类型的字节数,但是需要放在不安全的上下文中
8. 移位
右移:如果是整数补0,如果是负数补1
9. is和as
x is T:如果x是T,则返回true,否则返回false
x as T:类型转换,返回类型为T的x,如果x的类型不是T,则返回null
10. null合并
class Program
{
static void Main(string[] args)
{
int? x = null;
//如果x为null,则y的值为1,否则为x
int y = x ?? 1;
Console.WriteLine(y);
}
}
五. 语句
foreach语句
static void Main(string[] args)
{
foreach(string s in args)
{
Console.WriteLine(s);
}
}
yield语句
foreach (int i in ProduceEvenNumbers(9))
{
Console.Write(i);
Console.Write(" ");
}
// Output: 0 2 4 6 8
IEnumerable<int> ProduceEvenNumbers(int upto)
{
for (int i = 0; i <= upto; i += 2)
{
//在迭代中,提供下一个值
yield return i;
}
}
Console.WriteLine(string.Join(" ", TakeWhilePositive(new int[] {2, 3, 4, 5, -1, 3, 4})));
// Output: 2 3 4 5
Console.WriteLine(string.Join(" ", TakeWhilePositive(new int[] {9, 8, 7})));
// Output: 9 8 7
IEnumerable<int> TakeWhilePositive(IEnumerable<int> numbers)
{
foreach (int n in numbers)
{
if (n > 0)
{
yield return n;
}
else
{
//显示迭代结束
yield break;
}
}
}
六. 类和对象
1. 可访问性
2. 类型参数
class Test
{
static void Main(){
//使用泛型类时,必须为每个类型参数提供类型自变量
Pair<int,string> pair = new Pair<int, string> { First = 1, Second = "two" };
int i = pair.First;
string s = pair.Second;
}
//pair的类型参数是TFirst和TSecond
public class Pair<TFirst, TSecond>
{
public TFirst First;
public TSecond Second;
}
}
3. 基类和派生类
//Point的基类是object
public class Point
{
public int x, y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
}
//Point3D的基类是Point
public class Point3D : Point
{
public int z;
public Point3D(int x, int y,int z)
:base(x, y)
{
this.z = z;
}
}
//类类型的变量可以引用相应类的实例或任意派生类的实例
Point a = new Point(10,10);
Point b = new Point3D(10,10,20);
类继承基类的所有成员(实例和静态构造函数除外)和析构函数。
类类型的变量可以引用相应类的实例或任意派生类的实例。
4. 字段
- 字段是一种表示与对象或类型(类与结构体)关联的变量,也叫成员变量。与对象关联的字段称为实例字段;与类型关联的字段称为静态字段,由static修饰
- 无显示初始化时,字段获得其类型的默认值,所以字段永远不会未被初始化
- 实例字段初始化的时机:对象创建时;静态字段初始化的时机:类型被加载时
- 只读字段
readonly
5. 方法
静态方法(用static修饰)通过类访问,只能直接访问静态成员
实例方法通过类实例访问,能够访问静态和实例成员
6. 参数
- 值参数:修改值参数不会影响为其传递的自变量
-
值类型
-
引用类型,并且创建新对象
-
引用类型,只操作对象,不创建新对象
-
- 引用参数:用
ref
修饰,引用参数指的存储位置与自变量相同,必须对自变量进行明确赋值,用于需要修改实际参数值的场景 - 输出参数:用
out
修饰,与引用参数类似,调用方提供的自变量的初始值不重要,用于出返回值外还需要输出的场景 - 数组参数:用
params
修饰,参数数组只能是方法的最后一个参数
class Test
{
static void Main(string[] args)
{
int res = CaculateSum(1,2,3);
Console.WriteLine(res);
}
static int CaculateSum(params int[] arr)
{
int sum = 0;
foreach (int i in arr)
{
sum += i;
}
return sum;
}
}
- 具名参数:提高代码的可读性,参数位置不再受参数列表设计的约束
static void Main(string[] args)
{
PrintInfo(name: "tom", age: 12);
}
static void PrintInfo(string name, int age)
{
Console.WriteLine("Hello {0},you are {1}", name, age);
}
7. 扩展方法(this参数)
不修改目标数据类型源码的情况下为目标数据类型“追加”方法
- 方法必须是共有的、静态的,即被
public static
修饰 - 必须是形参列表中的第一个,由this修饰
- 必须由一个静态类(一般类名为SomeTypeExtension)来统一收纳SomeType类型的扩展方法
class Test
{
static void Main(string[] args)
{
double x = 3.1415926;
Console.WriteLine(x.Round(2));
}
}
static class RoundExtension
{
public static double Round(this double x, int digits)
{
double res = Math.Round(x, digits);
return res;
}
}
Linq扩展方法
using System;
using System.Collections.Generic;
using System.Linq;
class Test
{
static void Main(string[] args)
{
List<int> list = new List<int>() { 3, 12, 14, 15 };
bool res = list.All(i => i > 10);
Console.WriteLine(res);
}
}
8. 虚方法、重写方法、抽象方法和方法重载
- 虚方法:用
virtual
修饰,调用虚方法时,为其调用方法的实例运行时类型决定了要调用的实际方法实现代码 - 重写方法:可以在派生类中重写虚方法,当实例方法中包含
override
时,该方法将重写具有相同签名的继承的虚方法 - 抽象方法:无实现的虚方法,
abstract
修饰,只允许在也声明abstract的类中使用,必须在所有非抽象派生类中重写抽象方法。抽象类专门作为基类来使用,具有解耦功能。 - 方法重载:同一类中可以有多个同名方法,只要这些方法具有唯一签名即可
9. 属性
属性是一种用于访问对象或类型的特征的成员,特征反映了状态。属性是字段的自然扩展。
ctrl + e+r自动生成属性或者右键->Refactor->Encapsulate Field->可以修改你想要的属性的名称->Okay
class Test
{
private int age;
public int Age
{
get { return age; }
set
{
if (value > 0 && value <= 120)
{
age = value;
}
else
{
throw new Exception("Age value has error");
}
}
}
}
10. 索引器
索引器使对象能够用与数组相同的方式(即使用下标)进行索引。
索引器可以重载。
11. 默认值
以下类别的变量会自动初始化为其默认值
- 静态变量
- 类实例的实例变量
- 数组元素
六. 结构
借助类,两个变量可以引用同一个对象,对一个变量执行的运算可能会影响另一个变量引用的对象;借助结构,每个变量都有自己的数据副本,对一个变量执行的运算不会影响另一个变量。
Point a = new Point(10, 10);
Point b = a;
a.x = 20;
//若Point为类,输出20,Point为结构,输出10
Console.WriteLine(b.x);
七. 数组
数组是引用类型,声明数组变量只是为引用数组实例预留空间,实际的数组实例是在运行时使用new创建。
C#允许"@"字符作为前缀以使关键字能够作为标识符
八. vs使用技巧
鼠标放在空行
,右键单击并选择快速操作和重构
菜单,就可以生成构造函数之类的。
ctrl+k+d:调整格式
ctrl + d:复制当前行到下一行