C# Syntax - Base

from: Tour of C#

Program Structure

  • applications
  • libraries
  • compiler

关于C#编译器的安装和使用,请参考另一篇博客:《C#环境搭建,以及C#编译器的使用》

Types and variables

C# 有两种类型:值类型 引用类型

C#’s value types are further divided into simple typesenum typesstruct types, and nullable value types. C#’s reference types are further divided into class typesinterface typesarray types, and delegate types.

Expressions

对于运算符的优先级顺序,建议访问官网教程查询。这里只对若干易混淆表达式作以摘录:

new T(...) 创建对象和委托
new T(...){...} 使用初始值设定项的对象创建
new {...} 匿名对象初始值设定项
new T[...] 数组创建
typeof(T) 获取 T 的 Type 对象
default(T) 获取类型为 T 的默认值
delegate {...} 匿名函数(匿名方法)
await x 异步等待 x 完成
x is T 如果 x 是 T,返回 true;否则,返回 false
x as T 返回类型为 T 的 x;如果 x 的类型不是 T,返回 null
(T x) => y 匿名函数(lambda 表达式)

Statements

Selection statements are used to select one of a number of possible statements for execution based on the value of some expression. In this group are the if and switch statements.

{
    int n = args.Length;
    switch (n) {
        case 0:
            Console.WriteLine("No arguments");
            break;
        case 1:
            Console.WriteLine("One argument");
            break;
        default:
            Console.WriteLine($"{n} arguments");
            break;
    }
}

Iteration statements are used to execute repeatedly an embedded statement. In this group are the whiledofor, and foreach statements.

{
    foreach (string s in args) {
        Console.WriteLine(s);
    }
}

Jump statements are used to transfer control. In this group are the breakcontinuegotothrowreturn, and yield statements.

{
    int i = 0;
    goto check;
    loop:
      Console.WriteLine(args[i++]);
    check:
      if (i < args.Length) 
          goto loop;
}
-------------------------------------------------------------------------
{
    for (int i = from; i < to; i++) {
        yield return i;
    }
    yield break;
}
static void YieldStatement(string[] args)
{
    foreach (int i in Range(-10,10)) {
        Console.WriteLine(i);
    }
}

The try...catch statement is used to catch exceptions that occur during execution of a block, and the try...finally statement is used to specify finalization code that is always executed, whether an exception occurred or not.

{
    if (y == 0) 
        throw new DivideByZeroException();
    return x / y;
}
static void TryCatch(string[] args) 
{
    try {
        if (args.Length != 2) {
            throw new InvalidOperationException("Two numbers required");
        }
        double x = double.Parse(args[0]);
        double y = double.Parse(args[1]);
        Console.WriteLine(Divide(x, y));
    }
    catch (InvalidOperationException e) {
        Console.WriteLine(e.Message);
    }
    finally {
        Console.WriteLine("Good bye!");
    }
}

The checked and unchecked statements are used to control the overflow-checking context for integral-type arithmetic operations and conversions.

{
    int x = int.MaxValue;
    unchecked {
        Console.WriteLine(x + 1);  // Overflow
    }
    checked {
        Console.WriteLine(x + 1);  // Exception
    }     
}

The lock statement is used to obtain the mutual-exclusion lock for a given object, execute a statement, and then release the lock.

{
    decimal balance;
    private readonly object sync = new object();
    public void Withdraw(decimal amount) {
        lock (sync) {
            if (amount > balance) {
                throw new Exception(
                    "Insufficient funds");
            }
            balance -= amount;
        }
    }
}

The using statement is used to obtain a resource, execute a statement, and then dispose of that resource.

{
    using (TextWriter w = File.CreateText("test.txt")) 
    {
        w.WriteLine("Line one");
        w.WriteLine("Line two");
        w.WriteLine("Line three");
    }
}

Structs

Like classes, structs are data structures that can contain data members and function members, but unlike classes, structs are value types and do not require heap allocation. Struct types do not support user-specified inheritance, and all struct types implicitly inherit from type ValueType, which in turn implicitly inherits from object.

struct Point {
    public int x, y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

// use the struct
Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

如果 Point 是类,则输出 20,因为 a 和 b 引用同一对象。 如果 Point 是结构,则输出 10,因为将 a 赋值给 b 创建了值副本,而此副本不受后面对 a.x 的赋值的影响。

以上示例突出显示了结构的两个限制:

  • 复制整个结构通常比复制对象引用效率更低,因此通过结构进行的赋值和值参数传递可能比通过引用类型成本更高。
  • 除 inref 和 out 参数以外,无法创建对结构的引用,这就表示在很多应用场景中都不能使用结构。

Enums

枚举类型是包含一组已命名常量的独特值类型。枚举使用一种整型值类型作为其基础存储, 并提供离散值的语义含义。

enum Color {
    Red,
    Green,
    Blue
}

每个 enum 类型都有对应的整型类型(称为 enum 类型的基础类型)。 如果 enum 类型未显式声明基础类型,则基础类型为 int

enum Alignment: sbyte {
    Left = -1,
    Center = 0,
    Right = 1
}

可使用类型显式转换功能将 Enum 值转换成整型值,反之亦然。

任何 enum 类型的默认值都是已转换成 enum 类型的整型值 0。 如果变量被自动初始化为默认值,这就是为 enum 类型的变量指定的值。 为了让 enum 类型的默认值可供方便使用,文本类型 0 隐式转换成任意 enum 类型。

Arrays

数组类型是引用类型,声明数组变量只是为引用数组实例预留空间。 实际的数组实例是在运行时使用 new 运算符动态创建而成。 new 运算指定了新数组实例的长度,然后在此实例的生存期内固定使用这个长度。 数组元素的索引介于 0 到 Length - 1 之间。 new 运算符自动将数组元素初始化为其默认值(例如,所有数值类型的默认值为 0,所有引用类型的默认值为 null)。

int[] a1 = new int[10];
int[,] a2 = new int[10, 5];
int[,,] a3 = new int[10, 5, 2];

数组的元素类型可以是任意类型(包括数组类型)。 包含数组类型元素的数组有时称为交错数组,因为元素数组的长度不必全都一样。 以下示例分配由 int 数组构成的数组:

int[][] a = new int[3][];
a[0] = new int[10];
a[1] = new int[5];
a[2] = new int[20];

Classes and objects

Members

The members of a class are either static members or instance members. Static members belong to classes, and instance members belong to objects (instances of classes).

包括了 Constants, Fields(字段), Methods, Properties, Indexers(索引器), Events, Operators, Constructors, Finalizers, Types(Nested types declared by the class - 类声明的嵌套类型)。

Accessibility

Each member of a class has an associated accessibility, which controls the regions of program text that are able to access the member. There are six possible forms of accessibility. These are summarized below.

public Access not limited
protected Access limited to this class or classes derived from this class
internal Access limited to the current assembly (.exe, .dll, etc.)
protected internal Access limited to the containing class, classes derived from the containing class, or classes within the same assembly
private Access limited to this class
private protected Access limited to the containing class or classes derived from the containing type within the same assembly

Type parameters

public class Pair<TFirst,TSecond>
{
    public TFirst First;
    public TSecond Second;
}

// try to use it
Pair<int,string> pair = new Pair<int,string> { First = 1, Second = "two" };
int i = pair.First;     // TFirst is int
string s = pair.Second; // TSecond is string

Methods

A method is a member that implements a computation or action that can be performed by an object or class. Static methods are accessed through the class. Instance methods are accessed through instances of the class.

Parameters

Method Parameters are used to pass values or variable references to methods. The parameters of a method get their actual values from the arguments that are specified when the method is invoked. There are four kinds of parameters: value parameters, reference parameters, output parameters, and parameter arrays.

static void Swap(ref int x, ref int y) {
    int temp = x;
    x = y;
    y = temp;
}
public static void SwapExample() {
    int i = 1, j = 2;
    Swap(ref i, ref j);
    Console.WriteLine($"{i} {j}");    // Outputs "2 1"
}
-------------------------------------------------------------------------------
static void Divide(int x, int y, out int result, out int remainder) {
    result = x / y;
    remainder = x % y;
}
public static void OutUsage() {
    Divide(10, 3, out int res, out int rem);
    Console.WriteLine("{0} {1}", res, rem);    // Outputs "3 1"
}
-------------------------------------------------------------------------------
public class Console{ public static void Write(string fmt, params object[] args) { } public static void WriteLine(string fmt, params object[] args) { } // ... }

Virtual, override, and abstract methods

When an instance method declaration includes a virtual modifier, the method is said to be a virtual method. When no virtual modifier is present, the method is said to be a nonvirtual method.

When a virtual method is invoked, the run-time type of the instance for which that invocation takes place determines the actual method implementation to invoke. In a nonvirtual method invocation, the compile-time type of the instance is the determining factor.

public abstract class Expression {
    public abstract double Evaluate(Dictionary<string,object> vars);
}
public class Constant: Expression {
    double value;
    public Constant(double value) {
        this.value = value;
    }
    public override double Evaluate(Dictionary<string,object> vars) {
        return value;
    }
}

Method overloading

Method overloading permits multiple methods in the same class to have the same name as long as they have unique signatures. The signature of a method consists of the name of the method, the number of type parameters and the number, modifiers, and types of its parameters. The signature of a method does not include the return type.

Other function members

This section describes the other kinds of function members supported by C#: constructors, properties, indexers, events, operators, and finalizers.

 1 public class List<T>
 2 {
 3     // Constant
 4     const int defaultCapacity = 4;
 5 
 6     // Fields
 7     T[] items;
 8     int count;
 9 
10     // Constructor
11     public List(int capacity = defaultCapacity) {
12         items = new T[capacity];
13     }
14 
15     // Properties
16     public int Count => count; 
17 
18     public int Capacity {
19         get { return items.Length; }
20         set {
21             if (value < count) value = count;
22             if (value != items.Length) {
23                 T[] newItems = new T[value];
24                 Array.Copy(items, 0, newItems, 0, count);
25                 items = newItems;
26             }
27         }
28     }
29 
30     // Indexer
31     public T this[int index] {
32         get {
33             return items[index];
34         }
35         set {
36             items[index] = value;
37             OnChanged();
38         }
39     }
40     
41     // Methods
42     public void Add(T item) {
43         if (count == Capacity) Capacity = count * 2;
44         items[count] = item;
45         count++;
46         OnChanged();
47     }
48 
49     protected virtual void OnChanged() =>
50         Changed?.Invoke(this, EventArgs.Empty);
51 
52     public override bool Equals(object other) =>
53         Equals(this, other as List<T>);
54 
55     static bool Equals(List<T> a, List<T> b) {
56         if (Object.ReferenceEquals(a, null)) return Object.ReferenceEquals(b, null);
57         if (Object.ReferenceEquals(b, null) || a.count != b.count)
58             return false;
59         for (int i = 0; i < a.count; i++) {
60             if (!object.Equals(a.items[i], b.items[i])) {
61                 return false;
62             }
63         }
64     return true;
65     }
66 
67     // Event
68     public event EventHandler Changed;
69 
70     // Operators
71     public static bool operator ==(List<T> a, List<T> b) => 
72         Equals(a, b);
73 
74     public static bool operator !=(List<T> a, List<T> b) => 
75         !Equals(a, b);
76 }
View Code

Constructors

C# 支持实例和静态构造函数。 实例构造函数是实现初始化类实例所需执行的操作的成员。 静态构造函数是实现在首次加载类时初始化类本身所需执行的操作的成员。

与其他成员不同,实例构造函数不能被继承,且类中只能包含实际已声明的实例构造函数。 如果没有为类提供实例构造函数,则会自动提供不含参数的空实例构造函数。

Properties

属性是字段的自然扩展。 与字段不同的是,属性不指明存储位置。 相反,属性包含访问器,用于指定在读取或写入属性值时要执行的语句。

List<string> names = new List<string>();
names.Capacity = 100;   // Invokes set accessor
int i = names.Count;    // Invokes get accessor
int j = names.Capacity; // Invokes get accessor

Similar to fields and methods, C# supports both instance properties and static properties. Static properties are declared with the static modifier, and instance properties are declared without it.

The accessor(s) of a property can be virtual. When a property declaration includes a virtualabstract, or override modifier, it applies to the accessor(s) of the property.

Indexers

借助索引器成员,可以将对象编入索引(像处理数组一样)。 索引器的声明方式与属性类似,不同之处在于,索引器成员名称格式为后跟分隔符 [ 和 ],其中写入参数列表。 这些参数在索引器的访问器中可用。 类似于属性,索引器分为读写、只读和只写索引器,且索引器的访问器可以是的 virtual 修饰 。

List<string> names = new List<string>();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++) {
    string s = names[i];
    names[i] = s.ToUpper();
}

索引器可以进行重载。也就是说,类可以声明多个索引器,只要其参数的数量或类型不同即可。

Events

借助事件成员,类或对象可以提供通知。 事件的声明方式与字段类似,不同之处在于,事件声明包括事件关键字,且类型必须是委托类型

在声明事件成员的类中,事件的行为与委托类型的字段完全相同(前提是事件不是抽象的,且不声明访问器)。 字段存储对委托的引用,委托表示已添加到事件的事件处理程序。 如果没有任何事件处理程序,则字段为 null。

List<T> 类声明一个 Changed 事件成员,指明已向列表添加了新项。 Changed 事件由 OnChanged 虚方法引发,此方法会先检查事件是否是 null(即不含任何处理程序)。 引发事件的概念恰恰等同于调用由事件表示的委托,因此,没有用于引发事件的特殊语言构造。

客户端通过事件处理程序响应事件。 使用 += 和 -= 运算符分别可以附加和删除事件处理程序。

class EventExample
{
    static int changeCount;
    static void ListChanged(object sender, EventArgs e) {
        changeCount++;
    }
    public static void Usage() {
        List<string> names = new List<string>();
        names.Changed += new EventHandler(ListChanged);
        names.Add("Liz");
        names.Add("Martha");
        names.Add("Beth");
        Console.WriteLine(changeCount);        // Outputs "3"
    }
}

对于需要控制事件的基础存储的高级方案,事件声明可以显式提供 add 和 remove 访问器,这在某种程度上与属性的 set 访问器类似。

Operators

运算符是定义向类实例应用特定表达式运算符的含义的成员。 可以定义三种类型的运算符:一元运算符、二元运算符和转换运算符。 所有运算符都必须声明为 public 和 static

Finalizers

终结器既不能包含参数和可访问性修饰符,也不能进行显式调用。 实例的终结器在垃圾回收期间自动调用。

垃圾回收器在决定何时收集对象和运行终结器时有很大自由度。 具体来说,终结器的调用时间具有不确定性,可以在任意线程上执行终结器。For these and other reasons, classes should implement finalizers only when no other solutions are feasible.

处理对象析构的更好方法是使用 using 语句。

Interfaces

接口可以包含方法、属性、事件和索引器。 接口不提供所定义的成员的实现代码,仅指定必须由实现接口的类或结构提供的成员。

接口可以采用多重继承

类和结构可以实现多个接口。

当类或结构实现特定接口时,此类或结构的实例可以隐式转换成相应的接口类型。

EditBox editBox = new EditBox();
IControl control = editBox;
IDataBound dataBound = editBox;

一般情况下,实现类都是以public的方式实现接口中的方法。 C# 还支持显式接口成员实现代码,这样类或结构就不会将成员设为公共成员。 显式接口成员实现代码是使用完全限定的接口成员名称进行编写。如下示例:

public class EditBox: IControl, IDataBound {
    void IControl.Paint() { }
    void IDataBound.Bind(Binder b) { }
}

显式接口成员只能通过接口类型进行访问。 例如,只有先将 EditBox 引用转换成 IControl 接口类型,才能调用上面 EditBox 类提供的 IControl.Paint 实现代码。

EditBox editBox = new EditBox();
editBox.Paint();            // Error, no such method
IControl control = editBox;
control.Paint();            // OK

通过直接使用接口,您不会将代码耦合到底层实现。同样,显式接口实现处理命名或方法签名的模糊性 - 并使单个类可以实现具有相同成员的多个接口。

关于“显式接口实现”的具体使用和注意事项,请参考《C#: Favorite Features through the Years》的第一节:C# Version 1.0

Delegates

委托类似于其他一些语言中的函数指针概念,但与函数指针不同的是,委托不仅面向对象,还类型安全。

using System;
delegate double Function(double x);
class Multiplier { double factor; public Multiplier(double factor) { this.factor = factor; } public double Multiply(double x) { return x * factor; } }
class DelegateExample { static double Square(double x) { return x * x; } static double[] Apply(double[] a, Function f) { double[] result = new double[a.Length]; for (int i = 0; i < a.Length; i++) result[i] = f(a[i]); return result; } static void Main() { double[] a = {0.0, 0.5, 1.0}; double[] squares = Apply(a, Square); double[] sines = Apply(a, Math.Sin); Multiplier m = new Multiplier(2.0); double[] doubles = Apply(a, m.Multiply); } }

还可以使用匿名函数创建委托,这些函数是便捷创建的“内联方法”。 匿名函数可以查看周围方法的局部变量。 因此,可以更轻松地编写上面的乘数示例,而无需使用 Multiplier 类:

double[] doubles =  Apply(a, (double x) => x * 2.0);

委托的一个有趣且有用的属性是,它不知道也不关心所引用的方法的类;只关心引用的方法是否具有与委托相同的参数返回类型

Attributes

Attribute 用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。.Net 框架提供了两种类型的特性:预定义特性和自定义特性

C# 程序中的类型、成员和其他实体支持使用修饰符来控制其行为的某些方面。 例如,方法的可访问性是由 public、protected、internal 和 private 修饰符控制。 C# 整合了这种能力,以便可以将用户定义类型的声明性信息附加到程序实体,并在运行时检索此类信息。 程序通过定义和使用 Attribute 来指定此类额外的声明性信息。

using System;

public class HelpAttribute: Attribute{
    string url;
    string topic;
    public HelpAttribute(string url) {
        this.url = url;
    }

    public string Url => url;

    public string Topic {
        get { return topic; }
        set { topic = value; }
    }
}

所有特性类都派生自标准库提供的 Attribute 基类。

其实用方式如下:

[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/attributes")]
public class Widget
{
    [Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/attributes", 
    Topic = "Display")]
    public void Display(string text) {}
}

此示例将 HelpAttribute 附加到 Widget 类。 还向此类中的 Display 方法附加了另一个 HelpAttribute。 特性类的公共构造函数控制了将特性附加到程序实体时必须提供的信息。 可以通过引用特性类的公共读写属性(如上面示例对 Topic 属性的引用),提供其他信息。

于是乎,在客户端编程(使用 Widget 对象和 Display() 方法时):

class Demo{
    static void Main(){
        Type widgetType = typeof(Widget);

        //Gets every HelpAttribute defined for the Widget type
        object[] widgetClassAttributes = widgetType.GetCustomAttributes(typeof(HelpAttribute), false);

        if (widgetClassAttributes.Length > 0){
            HelpAttribute attr = (HelpAttribute)widgetClassAttributes[0];
            Console.WriteLine($"Widget class help URL : {attr.Url} - Related topic : {attr.Topic}");
        }

        System.Reflection.MethodInfo displayMethod = widgetType.GetMethod(nameof(Widget.Display));

        //Gets every HelpAttribute defined for the Widget.Display method
        object[] displayMethodAttributes = displayMethod.GetCustomAttributes(typeof(HelpAttribute), false);

        if (displayMethodAttributes.Length > 0){
            HelpAttribute attr = (HelpAttribute)displayMethodAttributes[0];
            Console.WriteLine($"Display method help URL : {attr.Url} - Related topic : {attr.Topic}");
        }
    }
}

 


 

实际上,关于Attribute,个人还么有看懂,只是照着官网照抄一通——关键不是很了解特性是用来干嘛的,对我们编程有什么优势,节约开销?省代码?具体的等自己慢慢成长吧——恩,等待我的还有反射、泛型等等啦。。。

存个教程,以后可能用得到: 菜鸟教程,个人觉得对Attribute解释得最好的。

posted @ 2018-10-01 16:29  daw1213  阅读(403)  评论(0编辑  收藏  举报