CIL, common intermediate language
JIT, just-in-time
GAC, global assembly cache, 用于”共享”代码
托管代码受CIL控制, 反之为非托管代码
C#的命名规范是, 局部变量与私有成员使用camelCasing命名法, 其他的均使用PascalCasing命名法
VS中的解决方案不仅是一个应用程序, 还可以包含其他项目
XAML是WPF中定义用户界面的语言
VS可通过///
后的XML格式注释提取文档
#
开头的关键字是预处理指令
指令指示VS可以折叠代码, 其中${text}是折叠后显示的文本:
#region ${text}
/*code here*/
#endregion
C# 6支持字符串$"The sum of {firstNumber} and {secondNumber} is {firstNumber + secondNumber}"
, 其中firstNumber
与secondNumber
是变量名
C#中的string
虽是引用类型, 却可以直接使用==
进行比较相等性(不同于Java的String
, 根本原因在于其支持运算符重载)
C#的string
类的static
方法format(string, params object[])
支持格式化字符串
\0
是null
的字符串字面值转义
C#字符串引号前加@
, 表示不进行转义,(除双引号), 这样的文本可以跨行(类似于Python字符串前的r
)
string
是不可变的, 对string
对象索引, 如str[2]
将返回对应的char
, string
的ToCharArray()
方法返回char[]
string
有Length
属性, string
有PadLeft(int)
与PadRight(int)
方法用于左右添加空格以达到参数长度(适用于字符串对齐), 或使用重载版本提供填充字符
引用类型可以被赋值为null
, 值类型不可以
C#中的整数除法是截断除法
C#代码默认情况下包含在全局名称空间中
System
名称空间是.NET Framework应用的根名称空间
C# 6新增using static指令, 允许直接包含static成员
&&
与||
会短路求值
C#中的switch
语句的case
块完成后没有break
转而跳到下一个case
是非法的
switch
语句中的case
可以堆叠使用, 相当于一次检查多个值, 如:
case 1:
case 2:
case 3:
// code here
break;
do…while(/*code*/);
语句后需要分号
C#关键词checked
与unchecked
指示强制类型转换时是否进行溢出检查, 如int num = checked((int)666L);
Convert.${ToType}(${sourceVar})
方法进行类型转换时总是checked
的, 如num = Convert.ToInt32(666L);
枚举类型的定义格式如下:
enum ${typeName} : ${underlyingType}
{
${value1} = ${actuaVal1},
${value2} = ${actuaVal2},
/* other members*/
${valueN} = ${actuaValN}
}
其中: ${underlyingType}
(默认为int
)与= ${actuaValN}
部分是可选的(默认从0开始), 定义枚举类型的一个定义的例子如下:
enum orientation : byte
{
north ,
sourth,
east = 0,
west
}
需要注意, 此时north, sourth, east, west的值byte类型表示为0, 1, 0, 1, 这会造成解析出错, 使用的一个例子如下:
orientation ori = orientation.west;
Console.WriteLine(@"Number of north is {0}.", checked((byte)orientation.north));
从字符串中解析enum的方法为orientation ori = (orientation)Enum.Parse(typeof(orientation), "east");
结构(struct), 是由几个数据组成的数据结构, 结构还可以包含构造函数、常量、字段、方法、属性、索引器、运算符、事件和嵌套类型(这样的话为什么不考虑用class呢? ), struct也可以继承或实现接口, struct是值类型, 而class是引用类型, 所以, 定义方式如下
struct ${typeName}
{
${accessibility} ${type} ${varName}; // struct不能继承, 所以${accessibility}不可为protected
${accessibility} ${type} ${varName};
/* other members*/
}
一个例子如下:
struct student
{
public int id, age;
public string name;
public string Info()
{
return "Name: " + name + "; Age: " + age;
}
}
一个使用的例子如下:
student stu; // 是的, 直接定义一个变量, 可以不需要new
stu.id = 1;
stu.age = 21;
stu.name = "seliote";
// 在所有域初始化前直接调用出错, 原因是使用了未初始化的变量
Console.WriteLine(stu.Info()); // 输出1
多维数组的定义: ${baseType}[,] ${varName}; // 没错, 逗号, N维数组用N - 1个逗号
, 一个例子如下:
double[,] doubleArray = {{1, 2}, {2, 3}, {3, 4}};
Console.WriteLine(doubleArray[0, 0]); // 输出 1
数组的数组(锯齿数组, 注意与多维数组的区分)的定义的一个例子:
int[][] intArray;
intArray = new int[2][];
intArray[0] = new int[3];
intArray[1] = new int[5];
foreach循环的格式为:
foreach (${baseType} ${varName} in ${array})
{
// use ${varName}, 需要注意, 此处对于${array}是只读访问
}
foreach对于多维数组可以直接应用到元素, 而对于数组的数组则需要嵌套使用foreach
所有C#可执行代码都必须有一个入口点, 控制台应用的入口点就是Main方法, 该方法执行完毕后, 应用程序就终止了, Main方法的返回值可以为void
或int
, 参数可以为空或string[]
, 所以有四种写法, int
类型的返回值用于错误码
C# 6引入了表达式体方法, 定义方式为${returnType} ${functionName}() => /* process data and return*/;
C#通过关键字params实现可变参数列表, 接受0至多个参数, 定义格式如下:
${returnType} ${functionName}(params ${type}[] ${name}) {/* code here*/}
其中params修饰的参数必须是参数列表的最后一个参数, 且其修饰的参数必须为数组类型
C#中的参数大部分时候是值传递, 改变形参状态不影响实参, 可以通过ref关键字使用类似与C++中的&引用传值, 使用方法是在需要引用传递的参数(形参与实参需要同时加上)类型前都加上ref关键字, 两个限制, 参数不能传入常量, 实参必须已初始化, 一个例子如下:
class Program
{
static void DoubleInt(ref int source)
{
source *= 2;
}
static void Main(string[] args)
{
int value = 666;
Console.WriteLine(value); // 输出 666
DoubleInt(ref value);
Console.WriteLine(value); // 输出 1332
}
}
C#中还有个out关键词, 用于表示输出参数, 使用方法与ref相同, 方法中对于out参数的操作将直接影响原对象, 与ref不同之处是, ref实参必须事先初始化, 而out参数无论实参是否初始化, 形参都该当作未初始化, 且控制离开该方法前必须对out参数数赋值, 一个例子如下:
class Program
{
static void MaxInt(out int maxValue, int[] intArray)
{
if (intArray.Length <= 0)
{
maxValue = -1;
}
else
{
maxValue = intArray[0];
foreach (int intNum in intArray)
{
if (maxValue < intNum)
{
maxValue = intNum;
}
}
}
}
static void Main(string[] args)
{
int[] intArray = {2, 6, -10, 19, 1, 2, 9, 2};
int maxValue;
MaxInt(out maxValue, intArray);
Console.WriteLine(@"Max value is {0}", maxValue); // 输出 19
}
}
ref与out也属方法的签名
委托是一种存储方法引用的类型, 可以定义委托的变量, 这样就可以调用委托引用的任何函数, 甚至可以当作参数传递, 定义方法与方法类似, delegate ${returnType} ${delegateName}(${args});
, 参数列表与方法一样, 但是没有函数体, 一个委托的例子如下:
class Program
{
delegate double ProcessDelegate(double param1, double param2);
// 如果使用表达式体方法, 则改为 static double Mulitipy(double param1, double param2) => param1 * param2;
static double Mulitipy(double param1, double param2)
{
return param1 * param2;
}
static double Divide(double param1, double param2)
{
return param1 / param2;
}
static void Main(string[] args)
{
ProcessDelegate processDelegate;
double param1 = 6;
double param2 = 8;
string input = Console.ReadLine();
if ('m' == (input.ToLower())[0])
{
// 与 processDelegate = Mulitipy;同效果
processDelegate = new ProcessDelegate(Mulitipy);
}
else
{
// 与 processDelegate = Divide;同效果
processDelegate = new ProcessDelegate(Divide);
}
Console.WriteLine(processDelegate(param1, param2));
}
}
debug版的程序包含符号信息, 比如跟踪未编译代码中使用的变量名, 此类信息位于.pdb文件下
using System.Diagnostics
命名空间的Debug.WriteLine(...)
与Trace.WriteLine(...)
可用于向View|Output窗口写入信息, GUI可以通过此种办法调试, 其中Debug版本只能用于Debug程序, release版本中该命令会自动消失
断言使用Debug.Assert(...)
与Trace.Assert(...)
, 其中Debug版本只能用于Debug程序, release版本中该命令会自动消失
一旦创建数组就不能更改设置长度, 数组是隐式的引用对象, C#中的数组是实现了System.Array
类的实例
调试时右击代码行"设置下一语句"或拖动黄色箭头可设置程序接下来执行的语句(可用于调试时向后移动)
C# 6 catch块可使用await
与async
关键字实现异步编程
C# 6 引入了when
关键字进行catch块异常过滤, 当catch类型匹配且when中的表达式为true时才会执行该catch块, 格式为catch(${exceptionType} ${exceptionName}) when (${expression}) {/* other code*/}
try块后catch或finally至少有一个
没有参数(也没小括号)的catch
表示匹配所有异常
使用属性的方法与使用字段是一致的, 属性不提供对数据的直接访问, 可以更好地控制数据访问, 类似于访问器方法, 属性也有访问性控制, 属性可以控制只读或只写
C#与.NET Framework中一切皆对象(比Java纯粹?)
所有类至少有一个构造函数, 如果没有自己写的话编译器会自动生成默认的构造函数
编译器会生成默认的析构函数, 变量超出作用域后, 代码中就不能访问了, 但是只有.NET执行垃圾回收后该对象才会真正的删除, 析构函数不能手动调用
C#可以有至多一个静态构造函数, 静态构造函数不能有访问修饰符, 不能带参数, 不能手动调用, 在首次创建类的实例或首次访问静态成员的时候自动调用, 静态构造函数最多只会调用一次, 一个例子:
using System;
using System.Diagnostics;
namespace ConsoleApplicationDemo
{
class Demo
{
private static string START_TIME;
static Demo()
{
START_TIME = DateTime.Now.ToString();
}
public static int Main()
{
Trace.WriteLine(START_TIME);
return 0;
}
}
}
静态类只能包含静态成员, 静态类不能包含实例构造函数, 最多只能包含一个静态构造函数, 不能实例化对象
C#的接口可含属性, 接口的所有成员都不允许使用访问修饰符, 默认都是public的, 但是如果需要隐藏接口的方法可以在实现方法时使用new
关键字, 接口中定义属性的方法与自动属性类似, 格式为${type} ${varName} { get; set;}
(可省略其中某个), 接口的方法不能含有实现, 接口不能包含字段, 构造函数, 析构函数, 静态成员, 常量, 接口的命名一般会在最开始多加一个I, 同样的, 可以声明接口的变量, 不能实例化接口
IDisposable
接口包含Dispose()
方法, 用于在垃圾回收之前释放资源using (${varName}) {/* code here*/}
或using (${className} ${varName} = new ${className}) { /* code here*/}
会在代码块完成后自动调用Dispose()
方法
C#中所有对象的共同基类是object
, object
是System.Object
类的别名
C#支持运算符重载
对象可以激活与使用事件, 事件可以在代码的其他部分起作用
internal
类似于Java中的包可见性, sealed
关键字用于类定义时类似于Java中的final, 抽象类用abstract
修饰, protected internal
成员只可以项目内部子类来访问, readonly
修饰表示该字段只能在初始化或构造函数中进行赋值, const
用来定义常量, const
成员也是是static
的(显示声明static
将产生错误), virtual
用于指示方法可以被重写, override
表示该方法是重写了的基类方法, extern
表示该方法的定义在其他地方, 一个例子如下:
namespace ConsoleApplicationDemo
{
protected internal abstract class AbstractClassDemo
{
// const成员默认是static的
public const string ConstVal = "Hello World!";
private readonly string ReadonlyVal = AbstractClassDemo.ConstVal;
public abstract void AbstractMethod();
protected virtual void VirtualMethod() { }
}
public sealed class Demo : AbstractClassDemo
{
internal static void Main() { }
public override void AbstractMethod()
{
throw new System.NotImplementedException();
}
protected sealed override void VirtualMethod()
{
base.VirtualMethod();
}
}
}
继承类或实现接口使用冒号, 编译器不允许子类的可访问性高于基类, 继承的格式(接口的继承格式与类继承格式类似)为class ${className} : ${baseClass}, ${otherInterface} { /* other code*/}
, 基类一定在实现的接口前, 一个例子:
namespace ConsoleApplicationDemo
{
public class BaseDemo { }
internal class Demo : BaseDemo, IDemo
{
public static void Main()
{
System.Console.WriteLine("Hello world!");
}
}
public interface IDemo { }
}
类与接口的可访问性默认都是internal
接口不是类, 所以并没有继承自System.Object
, 但System.Object
可以通过接口类型的变量来访问
System.Object
实现的ToString()
方法是返回命名空间限定的类名的string
, Equals(object)
方法是比较两个变量的引用, MemberwiseClone()
方法创建一个新实例并复制成员, 但是新成员的引用与源类相同(浅拷贝), 这个方法是protected
的, GetType()
方法返回一个System.Type
对象表示其类型, 还有GetHashCode()
函数, 作用与Java中类似
析构函数的定义是~
加上类名, 即~${className}() { /* code here*/}
, 调用这个析构函数之后还将隐式调用父类的析构函数
子类总是先调用父类的构造函数, 直至递归到System.Object
, 如果没有明确给出, 就调用父类的默认构造函数, 一般来说最好给类实现默认构造函数, 尤其是在需要序列化的类上, 调用父类构造函数的构造函数初始化器格式为${accessibility} ${className}(${arg1}) : base(${arg2} { /* code here*/}
, 调用自身的其他构造器的格式为${accessibility} ${className}(${arg1}) : this(${arg2} { /* code here*/}
VS在解决方案上右键, 选择"视图", "查看类图"可查看类的UML图示
类库, 一个只包含类及相关定义而没有Main入口点的项目, 在创建项目时类型选择"类库"即可, 类库项目编译为dll程序, "生成", "生成解决方案", 由于没有入口点所以不能运行, 在其他项目中使用时右击"引用", "添加引用", "浏览", 选择要添加的dll
IClonable
接口有一个Clone()
方法, 返回System.Object
, 如果需要深度复制可以实现此接口, 但是具体怎么实现并没有强制规定
属性拥有两个类似于方法的块, 一个用于获取属性(get), 一个用于设置属性(set), set
中会有一个value
变量, 这是一个类型与属性相同的外部提供的值, 可以控制属性的访问权限, 可以忽略其中的一个块来创建只读或只写属性, 属性可以使用virtual
, override
, abstract
修饰, 访问器可以有自己的访问性(不能高于属性的访问性), 一个例子如下:
private string police;
public virtual string Police
{
get
{
return police;
}
protected set
{
police = value;
}
}
C#提供自动属性, 无需指定字段, 字段在编译期间由编译器生成, 所以我们也不知道字段的名称, C# 6引入了自动属性初始化器, 一个例子如下:
public static string DefaultProp
{
get;
protected set;
} = "Hello World";
注意区别C#中对于方法的覆盖与重写(Java中是默认重写, 差异性很大), 覆盖丢失多态性, 重写保留多态性, 无论基类的成员是否为virtual
的, 子类都可以覆盖该成员, 如果子类方法没有override
修饰但与基类方法签名相同, 则默认覆盖基类的该方法, 重写的方法必须在基类中使用virtual
修饰, 且在子类中使用override
修饰, new
关键字用在方法定义中明确表示覆盖了基类的方法, 不用的话编译时会生成一个警告, 一个值得警惕的例子:
using System;
namespace ConsoleApplicationDemo
{
public class DemoBase
{
public virtual void Method()
{
Console.WriteLine("DemoBase");
}
}
public class DemoBase2 : DemoBase
{
// 没有使用override, 其实际效果是使用了new进行了覆盖
public new virtual void Method()
{
Console.WriteLine("DemoBase2");
}
}
public class Demo : DemoBase2
{
public override void Method()
{
Console.WriteLine("Demo");
}
static void Main()
{
DemoBase demoBase = new Demo();
demoBase.Method(); // 输出 DemoBase , 覆盖无法产生多态性
}
}
}
覆盖基类方法后, 如果将子类对象赋予基类变量, 并对基类变量调用了被覆盖的方法, 此时将不产生多态而会直接调用基类的被覆盖的方法
C#中基类的基类中方法声明为virtual
后, 无论该方法重写多少层, 都可以直接使用override
进行重写, 但是一旦哪个基类对该方法进行了覆盖, 其子类将无法重写原始的该方法而只能重写被覆盖过的
C#中使用关键字base
引用基类对象实例
实现接口方法的时候不能加关键字override
, 这是隐式实现的, 一个接口的示例如下:
namespace ConsoleApplicationDemo
{
public interface IInterface
{
void Method();
}
public class Demo : IInterface
{
public void Method() { } // 重写的方法
}
}
使用partial
修饰类定义, 就可以将一个类的定义分写在多个文件中, 每个文件中的定义都要使用partial
关键字, 但是对于类或接口的继承与实现只要出现在一个文件中后, 就会自动应用到所有文件
C#中也提供了ArrayList
类, ArrayList
索引符返回的将是object
, 使用时需要强制类型转换, 新建的ArrayList
没有现成的项, 也没有null引用的项, 要先调用Add(object)
方法后才能使用索引符, 使用示例如下:
using System;
using System.Collections;
namespace ConsoleApplicationDemo
{
public class Demo
{
static void Main()
{
ArrayList arrayList = new ArrayList();
// 如果在调用Add(object)之前使用索引符, 将导致未经处理的异常: System.ArgumentOutOfRangeException
// arrayList[0] = new Demo();
arrayList.Add(new Demo());
arrayList[0] = new Demo();
}
}
}
自定义的列表类可以派生自System.Collections.CollectionBase
类, 该类实现了IEnumerable
(迭代), ICollection
(获取个数, 并能将项复制到一个数组中), IList
(访问数据项)接口, 并提供了List
与InnerList
两个protected
的属性用于访问存储对象本身
C#允许运重载索引符, 一个示例如下:
namespace ConsoleApplicationDemo
{
public class Demo
{
private string field = null;
static void Main()
{
(new Demo())["boom"] = "dididi";
System.Console.WriteLine((new Demo())["boom"]); // 输出biubiubiu
}
public string this[string index]
{
get { return field == null ? "biubiubiu" : field; }
// value 是用户提供的值
set { field = value; }
}
}
}
自定义的字典类可以派生自System.Collections.DictionaryBase
, 该类实现了IEnumerable
与ICollection
接口, 并提供了Count
, Dictionary
与InnerHashtable
三个属性, 对该对象使用foreach将获取到一个DictionaryEntry
结构或KeyValuePair<T, U>
, 都有Key
与Value
两个成员
迭代器, 是一个代码块, 按顺序提供了要在foreach块中使用的所有值, 迭代器块的返回类型是IEnumerable
(迭代一个类成员时用的)或IEnumerator
(迭代一个类时, 调用GetEnumerator
返回的), 使用yield return ${value};
选择要在foreach中使用的值, 所有${value}
的类型用于编译器推断foreach中的元素类型, 不一致将产生异常, 中断foreach使用yield break;
, yield性能通常会比一次全部产生结果好一些, 一个迭代成员的示例如下:
using System;
using System.Collections;
namespace ConsoleApplicationDemo
{
public class Demo
{
// 迭代类成员用IEnumerable
public static IEnumerable SimpleList()
{
// foreach中的元素类型由编译器进行推断, 如果yield的类型不一致将导致System.InvalidCastException异常
yield return "H";
yield return "i";
yield return "!";
}
static void Main()
{
foreach (string element in SimpleList())
{
Console.Write(element);
}
}
}
}
一个迭代类的示例如下:
using System;
using System.Collections;
namespace ConsoleApplicationDemo
{
/// <summary>
/// 迭代产生素数
/// </summary>
public sealed class Demo
{
private readonly int mMin;
private readonly int mMax;
public Demo() : this(2, 100) { }
public Demo(int min, int max)
{
if (min < 2)
{
min = 2;
}
this.mMin = min;
this.mMax = max;
}
public IEnumerator GetEnumerator()
{
for (int num = mMin; num <= mMax; ++num)
{
bool isPrime = true;
for (int index = 2; index <= ((int)Math.Floor(Math.Sqrt(num))); ++index)
{
if (num % index == 0)
{
isPrime = false;
break;
}
}
if (isPrime)
{
yield return num;
}
}
}
static void Main()
{
Console.WriteLine("2至500内的质数:");
// foreach自动调用GetEnumerator()方法
foreach (int prime in (new Demo(2, 500)))
{
Console.Write(prime.ToString().PadRight(5));
}
}
}
}
C#进行类型比较时, 可以使用继承自object
的GetType()
与typeof(int)
的返回值(都是System.Type
类型)进行比较, 这是比较严格的类型, 如果是子类的话也是不相等的, 一个示例如下:
public class Demo
{
public static void Main()
{
// typeof运算符直接传入类名
if ((new Demo()).GetType() == typeof(Demo))
{
Console.WriteLine("类型匹配");
}
}
}
is
运算符用来检查对象是不是给定类型, 语法为${operand} is ${type}
, 当${operand}
时${type}
的实现或子类的实现或可以拆箱到该类型, 该语句则返回true
, 一个示例如下:
if ((new Demo()) is Demo)
{
Console.WriteLine("类型匹配");
}
运算符重载方法必须是static的(索引符除外), 格式是${accessibility} static ${returnType} operator ${symbol}(${args}) { /* code here*/}
, 一个例子如下:
public class Demo
{
private int mValue;
public Demo() : this(0) { }
public Demo(int i) { mValue = i;}
// 双参数
public static Demo operator -(Demo argument1, Demo argument2)
{
// 是的, 是一个类, 可以访问private域
int result = argument1.mValue - argument2.mValue;
return new Demo(result);
}
// 双混合参数
public static Demo operator -(Demo argument1, int argument2)
{
return new Demo(argument1.mValue - argument2);
}
// 单参数
public static Demo operator -(Demo argument)
{
return new Demo(-argument.mValue);
}
public override string ToString()
{
return mValue.ToString();
}
public static void Main()
{
Console.WriteLine(- (new Demo(7))); // 输出 -7
Console.WriteLine(new Demo(7) - 3); // 输出 4
}
}
C#允许重载类型转换运算符, 用于两个没有继承关系的类之间转换, implcit
表明是隐式转换, explicit
表明是显式转换, 一个例子如下:
namespace ConsoleApplicationDemo
{
public class IntClass
{
public int Value;
public static implicit operator DoubleClass(IntClass intClass)
{
DoubleClass doubleClass = new DoubleClass();
doubleClass.Value = intClass.Value;
return doubleClass;
}
}
public class DoubleClass
{
public double Value;
public static explicit operator IntClass(DoubleClass doubleClass)
{
IntClass intClass = new IntClass();
unchecked { intClass.Value = (int)doubleClass.Value; }
return intClass;
}
}
public class Demo
{
public static void Main()
{
DoubleClass doubleClass = new DoubleClass();
doubleClass.Value = 7.77D;
// 显式转换
IntClass intClass = (IntClass)doubleClass;
Console.WriteLine(intClass.Value); // 输出 7
// 隐式转换
doubleClass = intClass;
Console.WriteLine(doubleClass.Value); // 输出 7
}
}
}
如果重载了true
和false
运算符, 就可以用在布尔表达式中, +=
这类运算符不能重载(<=
可以重载), =
不能重载, &&
与||
不能重载, >
与<
必须成对重载, 有一个不成文的规定是, 重载==
与!=
时, 通常会重写Equals(object)
(确保参数是object
而不是其他类型, 否则将是重载, Java中也有这个问题)与GetHashCode()
以确保用户"殊途同归"
IComparable
与IComparer
接口.NET Framework中比较对象的标准形式, IComparable
在类自身中实现, 用于比较自身和该类另一个对象, 需要实现CompareTo(object obj)
方法, IComparer
实现在第三方类中, 用于比较任意两个对象, 需要实现Compare(object, object)
方法
as
运算符用于将对象转换为指定类型或装箱成指定类型, 否则返回null
(避免抛出异常), 格式为${operand} as ${type}
, 返回目标类型的对象或null
C#泛型使用System.Nullable<T>
类型提供了使值类型可以为null
的途径, T?
是System.Nullable<T>
的缩写, 示例:
Nullable<int> nullableInt = 1;
nullableInt = null;
// 与上面的语句等价
nullableInt = new Nullable<int>();
double? nullableDouble = 7.7D;
nullableDouble = new Nullable<double>();
nullableDouble = null;
??
是空接合运算符, 如果第一个操作数不为null
, 则值为第一个操作数, 否则为第二个操作数, 格式为${op1} ?? ${op2}
?.
Elvis运算符, 当左边操作数不为null
, 继续向右执行, 否则, 结果为null
, 格式为${op1}?.${op2}
C#中的泛型列表是List<T>
, 不支持菱形语法
Comparison<T>
这个委托类型用于排序方法, 格式为int method(T object1, T object2)
, Predicate<T>
这个委托用于搜索方法, 格式为bool method (T object)
, 一个使用例子如下:
using System;
using System.Collections.Generic;
namespace ConsoleApplicationDemo
{
public class Demo
{
public int Value
{
get;
set;
}
public Demo() : this(0) { }
public Demo(int value) { Value = value;}
public override string ToString() { return this.Value.ToString();}
// 用于Comparison<T>
public static int ComparisonDelegate(Demo demo1, Demo demo2)
{
if (demo1.Value < demo2.Value) { return -1;}
else if (demo1.Value > demo2.Value) { return 1;}
else { return 0;}
}
// 用于Predicate<T>
public static bool PredicateDelegate(Demo demo)
{
if (demo.Value == 7) { return true;}
return false;
}
public static void Main()
{
List<Demo> demoList = new List<Demo>();
Random random = new Random();
for (int i = 0; i < 1000; ++i)
{
demoList.Add(new Demo(random.Next(10)));
}
// 与下行等价demoList.Sort(new Comparison<Demo>(Demo.ComparisonDelegate));
demoList.Sort(Demo.ComparisonDelegate);
foreach (Demo demo in demoList) {
Console.WriteLine(demo.ToString());
}
// 与下行等价List<Demo> demoListValue7 = demoList.FindAll(new Predicate<Demo>(Demo.PredicateDelegate));
List<Demo> demoListValue7 = demoList.FindAll(Demo.PredicateDelegate);
foreach (Demo demo in demoListValue7)
{
Console.WriteLine(demo);
}
}
}
}
Dictionary<K, V>
中存储重复键会抛出ArgumentException
, 其构造函数允许接受一个IComparer<K>
接口, C# 6引入了索引初始化器, 一个示例如下:
new Dictionary<int, string>()
{
[1] = "a",
[2] = "b"
}
C#创建泛型类格式为class ${className}<T1, T2, T3> {}
, 我们只能对泛型类型进行只读访问, 比如new T1()
将无法执行, 因为只能将泛型类型看作object
或可以封装进object
的类型, 不能做任何假设, 比如有默认构造函数, 比如重载了==
, 再比如可以赋null
, C#中所有泛型操作都是在运行期(C++是在编译期)
C#中给泛型类型赋予默认值需要使用default
, 格式为${var} = default(T1)
, 这将按适合的方式将${var}
复制为null
或0
C#中泛型类的类型约束使用where
关键字实现, 可提供多个约束格式为class ${className} : ${baseClassName} , ${interfaceName} where T1 : constraint1, constraint2 where T2 : constraint3 { /* code here*/}
, 不仅可以使用类与接口名作为约束, 还可以使用struct
(类型必须是值类型), class
(类型必须是引用类型), new()
(类型必须有公共无参构造器, 如果有, 必须在最后面), 甚至可以直接将一个泛型类型参数用作另一个泛型类型的约束(裸类型约束, 注意不要循环约束, 否则无法编译), 一个例子如下:
public class Base {}
public class Demo<T> where T : Base, new()
{
T field;
T method() { return field; }
}
可以继承关闭的泛型类(class Demo : List<Base> { /* code here*/}
), 也可以继承打开的泛型类(class Demo<T> : List<T> { /* code here*/}
), 打开的继承自基类的泛型类型的约束只能是基类约束的子集
定义泛型struct, 格式为struct ${name}<T1, T2> { /* code here*/}
泛型方法的约束也是使用关键字where
, 格式为${accessibility} R ${methodName}<R, Q>(${args}) where R : Q { /* code here*/}
, 使用方法与Java类似
泛型委托与泛型方法头类似, 只不过多了delegate
关键字
C#中的子类与父类分别用作泛型参数的泛型类并不存在继承关系(与Java一致), 协变(类似于Java的extends, 只能用于返回值与只读), 就是用来解决这类问题的, 在定义泛型类时使用out
关键字修饰泛型参数即可, 抗变(类似于Java中的super, 只能用于方法参数), 在定义泛型类时使用in
关键字修饰泛型参数即可
可以给命名空间设置别名, 格式为using ${aliasName} = ${namespace}
, 一个例子:using nsAlias = RootNamespace.BulingBulingNamespace
命名空间与别名名称冲突的时候, 名称空间优于别名, 这时可以使用::
运算符强迫编译器使用using语句定义的别名, global
是顶级命名空间的别名, 如global::System.Collections.Generic.List<int>
C#中的异常基类是System.Exception
, 自定义的异常应由此派生
事件类似于异常, 都是由对象抛出, 但是是由代码订阅这个事件, 单个事件可以有多个订阅, 事件处理代码的唯一限制是它必须匹配事件所要求的返回类型和参数, 这是事件定义的一部分, 由一个委托指定, 一个处理事件的例子:
using System;
using System.Timers;
namespace ConsoleApplicationDemo
{
public class Demo
{
private static int Count = 0;
private const string WriteString = @"abcdefghijk";
public static void Main()
{
Timer timer = new Timer(100);
// 与下行作用相同, 添加事件使用 += 运算符, timer.Elapsed += Demo.WriteChar;
// 达到制指定间隔引发事件, 使用ElapsedEventHandler委托进行指定
timer.Elapsed += new ElapsedEventHandler(Demo.WriteChar);
timer.Start();
System.Threading.Thread.Sleep(200);
Console.ReadKey();
}
// ElapsedEventHandler委托的两个参数
// object是引发事件的对象的引用
// ElapsedEventArgs是由事件传送的参数
public static void WriteChar(object source, ElapsedEventArgs elapsedEventArgs)
{
System.Console.Write(WriteString[Count++ % WriteString.Length]);
}
}
}
自定义事件的步骤是, 1: 创建用于该事件的委托类型(也可以使用已有的类型, 如EventHandler
); 2: 使用event关键字并指定类型为已声明的委托类型, 定义一个事件变量; 3: 使用+=
添加一个事件; 4: 定义事件处理程序, 签名与返回值要与委托相同; 5: 引发事件, 直接对委托变量进行调用以处理事件. 一个例子如下:
using System;
using System.Threading;
namespace ConsoleApplicationDemo
{
public delegate void MessageHandle(string msg);
public class Demo
{
private event MessageHandle messageHandle;
private void DisplayMessage(string msg)
{
Console.WriteLine("Received message: " + msg);
}
public static void Main()
{
Demo demo = new Demo();
demo.messageHandle += new MessageHandle(demo.DisplayMessage);
while (true)
{
Thread.Sleep(1000);
demo.messageHandle(DateTime.Now.ToString() + ", hello seliote!");
}
}
}
}
C#也支持反射, 可以运行时动态检查类型信息
C#中提供了类似与Java的注解, 叫做"特性", 使用格式是在需要使用特性的类或方法等的上一行加上[${attrName}($args)]
获取一个类的特性需要使用该类的Type
对象的GetCustomAttributes(true)
返回一个object[]
自己创建的特性需要派生自System.Attribute
, 创建的特性需要指定AttributeUsage
特性, 指定应用目标(多个值用|
隔开)与是否允许多次使用, 一个例子如下:
using System;
namespace ConsoleApplicationDemo
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class AttributeDemo : Attribute
{
public int Times { get; private set;}
public AttributeDemo(int times)
{
Times = times;
}
}
[AttributeDemo(100)]
public class AttributeUseDemo
{
public static void Main()
{
object[] customAttributes = typeof(AttributeUseDemo).GetCustomAttributes(true);
foreach (object attribute in customAttributes)
{
Console.WriteLine("Attribute " + attribute.ToString() + " had found!"); // 输出 Attribute ConsoleApplicationDemo.AttributeDemo had found!
AttributeDemo attributeDemo = attribute as AttributeDemo;
if (attributeDemo != null)
{
Console.WriteLine("--- attributeDemo.Times: " + attributeDemo.Times); // 输出 --- attributeDemo.Times: 100
}
}
}
}
}
对象初始化器, 无需在类中添加额外代码进行类初始化的方法, 需要为每个需要初始化的公开可访问的属性提供键值对, 如果省略类名后的括号则默认调用无参构造函数, 对象初始化器可嵌套使用, 先执行构造函数再执行初始化器, 格式为${className} ${var} = new ${className} {${propertyOrField1} = ${value1}, ${propertyOrField2} = ${value2}}
, 一个例子如下:
using System;
namespace ConsoleApplicationDemo
{
public class InitClass
{
public string Message;
public int Times;
private bool isUsed;
public string UserName { get; set; }
public InitClass() { }
public InitClass(bool aIsUsed, string userName)
{
isUsed = aIsUsed;
UserName = userName;
}
}
public class Demo
{
public static void Main()
{
InitClass initClass = new InitClass(false, "liote") { Message = "Hello", Times = 7, UserName = "seliote"};
Console.WriteLine(initClass.UserName); // 输出 seliote
}
}
}
C#支持类型推断, 使用关键字var
, 由编译器推倒变量类型(类似于C++ 11的关键字auto
), 用var
声明变量时必须初始化, 否则编译器将无法确定实际类型
匿名类型是C#编译器通过要存储的属性自动创建类型, 匿名类型中的属性是只读的, 格式为var ${varName} = new {${attributeName1} = ${value1}, ${attributeName2} = ${value2}}
C#支持动态类型, 使用场景一般是在处理非.NET对象, 使用dynamic
关键字, 可用作方法返回值, 这种类型的变量无论调用什么方法都会通过编译, 但是有可能在运行时抛出RuntimeBinderException
, 这种类型仅在编译期存在, 在运行期会被替换成object
C#支持参数默认值, 也可以通过[Optional]
修饰参数从而使用可选参数(将无法提供默认值)
C#支持实参的命名参数, 参数名与值通过:
分隔, 一个例子:
int result = this.method("seliote", age : "20")
C#支持匿名方法, 格式为delegate(${args}) { /* code here*/}
, 匿名方法中访问的局部变量会自动变为外部变量(超出作用域后也不一定删除, 生命周期随匿名方法)
C#中支持lambda, 配合delegate使用挺不错的, 需要注意lambda的参数可以不指定类型, 编译器会自己推断, 如果只有一个参数可以省略括号, 没有参数只写一个空括号即可, 表达式只有一个返回语句时可以省略return
, 返回值类型编译器也会自动推断, 如果多语句且有返回值则不能省略return
且需要用大括号括起所有语句, 一个例子:
public class Demo
{
public delegate void PerformDelegate(string userName, int age);
public static void PerformMethod(PerformDelegate performDelegate)
{
performDelegate("seliote", 21);
}
public static void Main()
{
// 存储 lambda
PerformDelegate performDelegate = (userName, age) => Console.WriteLine(userName + " is " + age + " years old!");
PerformMethod(performDelegate); // // 输出 seliote is 21 years old!
}
}
C#的文件处理主要依赖于以下几个类:
File
: 静态实用类, 文件相关处理, 移动复制删除文件等;
Directory
: 静态实用类, 目录相关处理, 移动复制删除目录等, 其static
方法SetCurrentirectory()
方法可设置当前工作目录;
Path
: 路径相关处理;
FileInfo
: 表示具体的物理文件, 包含处理该文件的方法;
DirectoryInfo
: 表示具体的物理目录, 包含处理该目录的方法;
FileSystemInfo
: FileInfo
与DirectoryInfo
的基类, 利于多态性可同时处理文件与目录;
FileSystemWatcher
: 用于监控文件和目录, 提供了这些文件和目录发生变化时应用可以捕获的事件.
C#中的流主要有以下几个类:
FileStream
: 可写或可读, 或二者均可, 可以同步或异步的读写文件, 构造函数参数2与3分别为FileMode
与FileAccess
的枚举值, 该类支持文件随机访问;
StreamReader
: 从流中以字符的方式读取, 使用FileStream
作为基类创建;
StreamWriter
: 向流中以字符方式写入, 使用FileStream
作为基类创建;
C#的Decoder
类可以将byte[]
转化为char[]
, Encoder
可以将char[]
转化为byte[]
, 一个Decoder
配合FileStream
例子:
using System;
using System.IO;
using System.Text;
namespace ConsoleApplicationDemo
{
public class Demo
{
public static void Main()
{
byte[] fileByte = new byte[200];
char[] fileChar = new char[200];
try
{
FileStream fileStream = new FileStream(@"./../../Program.cs", FileMode.Open);
fileStream.Seek(0, SeekOrigin.Begin);
Decoder decoder = Encoding.UTF8.GetDecoder();
while (fileStream.Read(fileByte, 0, fileByte.Length) > 0)
{
decoder.GetChars(fileByte, 0, fileByte.Length, fileChar, 0, true);
Console.Write(fileChar);
// byte[] 中的数据不会自动清除, 如果没有这步, 后面会有之前读取的多余的数据
fileByte.Initialize();
}
fileStream.Close();
}
catch (IOException exp)
{
Console.WriteLine("Eroor: " + exp.ToString());
}
}
}
}
string
是System.String
的别名, C#中有类似Python的字符串字面值暂留, 相同字面值只创建一次, String.Empty
与""
之间没有差别, String
提供了许多static
方法, 判断字符串是否为空最好使用static
方法String.IsNullOrEmpty()
, 如果需要大量拼接字符串, 可以使用StringBuilder
C#中正则元字符有: .
, 除\n
外的任何字符; [ ]
, 匹配括号内的任一字符, 可用-
指定范围; [^ ]
, 匹配任何一个括号内以外的字符; ^
, 表示行首; $
, 表示行尾; \w
, 单词字符, 与[a-zA-Z0-9]
等价, \W
, 与[^\w]
等价; \s
, 空白字符, 与[\n\t\r\f]
等价; \S
, 非空白字符, 与[^\s]
等价; \d
, 十进制数, 与[0-9]
等价; \D
, 匹配非十进制数, 与[^\d]
等价; *
, 零至多个前面的元素; +
, 一至多个前面的元素; ?
, 零个或一个前面的元素; {n}
, n个前面的元素; {n, }
, 至少n个前面的元素; {n, m}
, n至m个前面的元素; |
, 两个表达式之一; ( )
, 定义一个未命名的捕获组; (?<name>) (?'name')
, 定义一个命名捕获组; (?<number>) (?'number')
, 定义一个编号捕获组
C#中的正则是通过using System.Text.RegularExpressions
中的几个类实现的, Regex
类提供了实例成员和静态成员, 创建Regex
实例时正则表达式引擎不会编译和缓存表达式模式, 而使用静态方法则会编译和缓存表达式模式(默认缓存15个模式), 所以大量使用同一模式时应该使用静态方法, 一个例子:
using System;
using System.Text.RegularExpressions;
namespace ConsoleApplicationDemo
{
public class Demo
{
public static void Main()
{
string input = "123 321 456 789 222 012 752 155 695";
string pattern = @"\d2\d";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine(match.Value);
}
}
}
}
C#与MySQL的交互, 先添加MySQL.Data.dll(位于MySQL安装目录下的Connector NET 8.0\Assemblies${version}目录下)引用, 之后代码中使用步骤如下(未使用预处理):
using System.Web.Mvc;
using MySql.Data.MySqlClient;
namespace WebDemo.Controllers
{
public class DemoController : Controller
{
public string ExecuteSQL()
{
// 注意 charset 与 sslmode, 否则会造成乱码与连接出错
string constructorString = string.Format("Server={0};User Id={1};Password={2};PersistSecurityInfo=True;Port={3};DataBase={4};Charset={5};SslMode=none",
"localhost",
"root",
"password",
"3306",
"DemoDataBase",
"utf8mb4");
MySqlConnection mySqlConnection = new MySqlConnection(constructorString);
mySqlConnection.Open();
// 使用预处理的占位符, 参数2传入了 MySqlConnection 对象
MySqlCommand mySqlCommand = new MySqlCommand(
"INSERT INTO `DemoTable` (`row_id`, `title`, `author`, `introduction`, `create_date`) VALUES (UUID(), ?title, ?author, ?introduction, ?createDate);",
mySqlConnection);
// 创建预处理信息
MySqlParameter titleParameter = new MySqlParameter("?title", MySqlDbType.VarChar, 50);
MySqlParameter authorParameter = new MySqlParameter("?author", MySqlDbType.VarChar, 30);
MySqlParameter introductionParameter = new MySqlParameter("?introduction", MySqlDbType.VarChar, 1000);
MySqlParameter createDateParameter = new MySqlParameter("?createDate", MySqlDbType.Date);
// 绑定数据
titleParameter.Value = "少有人走的路";
authorParameter.Value = "派克";
introductionParameter.Value = "心智成熟之旅";
createDateParameter.Value = "2018-06-20";
// 添加绑定
mySqlCommand.Parameters.Add(titleParameter);
mySqlCommand.Parameters.Add(authorParameter);
mySqlCommand.Parameters.Add(introductionParameter);
mySqlCommand.Parameters.Add(createDateParameter);
// 预处理
mySqlCommand.Prepare();
// MySqlCommand 的 ExecuteNonQuery 方法
string executeResult = mySqlCommand.ExecuteNonQuery() > 0 ? "执行成功" : "执行失败";
// 多次执行, 无需重复绑定与预处理, 直接替换数据即可
titleParameter.Value = "穷爸爸富爸爸";
authorParameter.Value = "清崎";
introductionParameter.Value = "资产与负债";
createDateParameter.Value = "2018-06-20";
executeResult += " | " + (mySqlCommand.ExecuteNonQuery() > 0 ? "执行成功" : "执行失败");
// 记得关闭资源
mySqlConnection.Close();
return executeResult;
}
public string QueryFromDB()
{
string constructorString = string.Format("Server={0};User Id={1};Password={2};PersistSecurityInfo=True;Port={3};DataBase={4};Charset={5};SslMode=none",
"localhost",
"root",
"password",
"3306",
"DemoDataBase",
"utf8mb4");
MySqlConnection mySqlConnection = new MySqlConnection(constructorString);
mySqlConnection.Open();
MySqlCommand mySqlCommand = new MySqlCommand(
"SELECT `title`, `author`, `introduction` FROM DemoTable;",
mySqlConnection);
// SELECT 与 INSERT, DELECT, UPDATE 的不同之处
MySqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();
string displayString = "";
// MySqlDataReader 的 Read() 方法用于将 MySqlDataReader 移动到下一个记录, 需要注意使用第一行记录前也需要先 Read()
while (mySqlDataReader.Read())
{
// 使用索引符来访问数据
displayString += "Book Name: " + mySqlDataReader[0] + " Author: " + mySqlDataReader[1] + " Introduction" + mySqlDataReader[2] + "<br/><br/>";
}
// 关闭资源
mySqlDataReader.Close();
mySqlConnection.Close();
return displayString;
}
}
}