UP | HOME

Programing in CSharp

Table of Contents

csharp language note.

<!– more –>

The C# Language

C#语言基础

C#有两种类型内置类型和用户自定义类型。
C#还可以分为值类型和引用类型。这两种类型的区别为值在内存中的存储方式不同。
值类型在栈分配的内存中保存其实际值(或者作为更大的引用类型对象的一部分分配)。
引用类型变量的地址保存在栈中,但实际对象存在堆中。
C#还支持 C++风格的指针。

int i = 10;
int j = 10;
Console.WriteLine ("object.ReferenceEquals(i,j) = {0}", object.ReferenceEquals (i, j));
// output: False
// 在 object.ReferenceEquals(i,j) 这一步会对 i,j 分别进行装箱操作

内置类型

C#中内置类型符合.Net CLS 规范。

类型 大小/字节 .Net 类型 说明
byte 1 Byte 无符号(0~255)
char 2 Char Unicode 字符
bool 1 Boolean true 或者 false
sbyte 1 Sbyte 有符号(-128~127)
short 2 Int16 有符号 short(-32768~32767)
ushort 2 Uint16 无符号 short(0~65535)
int 4 Int32 有符号 int(-2147483648~2147483647)
uint 4 Uint32 无符号 int(0~4294967295)
float 4 Single 浮点数
double 8 Double 双精度浮点数
decimal 12 Decimal 固定精度
long 8 Int64  
ulong 8 Uint64  
指针类型

为了保持类型安全,默认情况下,C# 不支持指针运算。不过,通过使用 unsafe 关键字,可以定义可使用指针的不安全上下文。
在公共语言运行时 (CLR) 中,不安全代码是指无法验证的代码。C# 中的不安全代码不一定是危险的;只是其安全性无法由 CLR 进行验证的代码。 因此,CLR 只对在完全受信任的程序集中的不安全代码执行操作。 如果使用不安全代码,由您负责确保您的代码不会引起安全风险或指针错误。
我们在运行 unsafe 代码是要在项目属性-生成选项里配置下"允许运行不安全代码"。
参考资料: http://www.cnblogs.com/ydchw/p/3734453.html?utm_source=tuicool&utm_medium=referral

  //Tips:
  //在 csproj 文件中 PropertyGroup 字段下添加<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

  using System;
  struct Point
  {
      public int x, y;
  }

  class MainClass
  {
      unsafe static void Main()
      {
          Point pt = new Point();
          Point* pp = &pt;
          pp->x = 123;
          pp->y = 456;
          Console.WriteLine ( "{0} {1}", pt.x, pt.y );
      }
      static void Test()
      {
          unsafe {
              UnsafePoint pt = new UnsafePoint ();
              UnsafePoint* pp = &pt;
              pp->x = 123;
              pp->y = 456;
              Console.WriteLine ("{0} {1}", pt.x, pt.y);
          }
      }
  }
可以为 null 的值类型

普通的值类型不能有 null 值。 但是,可以通过在类型后面附加 ? 来创建可以为 null 值的类型。

判断是否为 NaN 和 Infinity

在 C#的浮点数计算中,0 除以 0 将得到 NaN,正数除以 0 将得到 PositiveInfinity,负数除以 0 将得到 NegativeInfinity。

if (float.IsInfinity(1.0f/0))
{
    Debug.Log("1.0f/0 is infinity");
}
if (float.IsNaN(0.0f/0))
{
    Debug.Log("0.0f/0 is NaN");
}

变量初始化 赋值

C#要求变量在使用前必须初始化或者赋值。
const 为编译期常量 ,readonly 为运行时常量

枚举类型

每个枚举类型都有底层类型,可以是任意整数类型(integer,short,long),但 char 除外。默认情况下枚举类型的底层类型为 int。
[性质][修饰符] enum 标识符 [:基础类型]
{枚举列表};

枚举遍历
  enum SomeValues
  {
      Value_0,
      Value_1,
      Value_20 = 20,
      Value_21
  }
  // 遍历枚举值
  foreach (GViewType type in GViewType.GetValues(typeof(GViewType))) {
      InitView_ (type);
  }
  // 需要注意的是 GViewType.GetValues(typeof(GViewType))返回的数组是按照名称字符串排序的,所以要按照枚举值遍历需要如下操作:
  var enums = GViewType.GetValues(typeof(GViewType));
  Array.Sort(enums);
  foreach(var eValue in enums)
  {
       // do something
  }
定义多个范围的枚举
  // 定义多个范围关联的枚举
  enum EnumType
  {
      kFirstEnumStart = 100,
      kFirstEnumEnd   = 200,
      kSecondEnumStart = 201,
      kSecondEnumEnd   = 300
  }
  enum FirstEnum
  {
      kFirstEnumStart,
      kFirstEnumOne,
      kFirstEnumTwo,
      // ...
      kFirstEnumEnd,
  }
  enum SecondEnum
  {
      kSecondEnumStart,
      kSecondEnumOne,
      kSecondEnumTwo,
      // ...
      kSecondEnumEnd,
  }
通过 Enum 的名称字符串判断 Enum 是否定义
  string netEventIDStr = "Cmd_" + methodInfo.Name.Replace(_netEventPrefix, "");
  if( Enum.IsDefined(typeof(NETWORK.Command), netEventIDStr) )
  {
      // do something
  }

string int Enum 互转
enum Colors { Red, Green, Blue, Yellow };

// 1 Enum-->String
// 返回 "Green"
var greenName = Colors.Green.ToString();
var greenName = Enum.GetName(typeof(Colors), Colors.Blue);
var colorsNames = Enum.GetNames(typeof(Colors));

// 2 String-->Enum
var redEnum = (Colors)Enum.Parse(typeof(Colors), "Red");

// 3 Enum-->int
(int)Colors.Red;
(byte)Colors.Green;

// 4 int-->Enum
Colors color = (Colors)2;
Colors color = (Colors)Enum.ToObject(typeof(Colors), 2);

运算符

&符号
// 1 位与操作符
Console.WriteLine ("x&y = {0}", 15 & 3);
// 2 取引用操作符
unsafe {
    int i = 10;
    int* j = &i;
}
// 3 && 逻辑运算符 且
if (a>b && a<c)
{
}
?符号
// 1 修饰基础类型使其可为 null
int? i = null;
// 2 三元运算符 条件表达式 ? 表达式 1 : 表达式 2
int a = 3>4 ? 3 : 4; // a=4;
??

如果 ?? 运算符的左操作数非空,该运算符将返回左操作数,否则返回右操作数。

条件分支语句

C/C++中条件语句中可以使用任意表达式,C#要求所有条件表达式都必须为布尔值.这是为了避免 if(a=10) 这样的错误的!

switch 语句可以对字符串进行跳转。

  switch(name)
   {
       case "Mr.A":
           DoSomething();
           break;
       case "Mr.B":
           DoOtherthing();
           break;
       default:
           break;
   }

预处理指令

C#预处理器只实现了 C++预处理器的一个子集,不支持宏。
#region [comment context]- #endregion 使用注释来标记一段代码.

#region test region
//test region
//a define is here
int a = 10;
#endregion

函数

泛型函数
static void ForeachObjInScene<ObjT>(string scenePath, bool includeInactive, float sceneProgress, System.Action<ObjT> opt) where ObjT : UnityEngine.Object
{
    if (opt == null) return;

    var info = string.Format("Progessing {0}", scenePath);
    if (EditorUtility.DisplayCancelableProgressBar("Modify Light Param", info, sceneProgress))
    {
    }

    var scene = EditorSceneManager.OpenScene(scenePath);
    if (scene == null)
    {
        Debug.LogWarningFormat("scene not exist! {0}", scenePath);
    }

    var objArr = GameObject.FindObjectsOfType<ObjT>(includeInactive);
    foreach(var obj in objArr)
    {
        opt(obj);
    }

    if (!EditorSceneManager.SaveScene(scene))
    {
        Debug.LogWarningFormat("Save Scene Failed! scene = {0}", scenePath);
    }
    if (!EditorSceneManager.CloseScene(scene, true))
    {
        Debug.LogWarningFormat("Close Scene Failed! scene = {0}", scenePath);
    }
}
函数可变参数

类与对象

定义类

[attribue 性质][修饰符]class 标识符[:基类]
{类主体}
C#类定义后不需要分号,加分号也不会报错。

访问修饰符
访问修饰符 限制
public 无限制。标记为 public 成员,任何类的任何方法都可以访问
private 标记为 private 的类 A 的成员,只有类 A 的方法可以访问
protected 标记为 protected 的类 A 的成员,类 A 和从类 A 派生的类的方法可以访问
internal 标记为 internal 的类 A 的成员,A 所处的程序集中的任何类的方法都可以访问
protected internal protected 或 internal 的意思。(没有 protected 且 internal 的概念)
protected internal

protected internal 表示 类和子类可以访问,该成员所在程序集内的任何类也可以访问。protected internal 范围大于等于 internal,也大于等于 protected。
http://www.cnblogs.com/adodo1/p/4327581.html

创建对象

对象是引用类型,创建于堆中,需要使用关键字 new。

构造方法

未定义构造函数时,编译器会提供一个默认构造函数。
未显式初始化的成员变量会按照下表的值初始化。
用户实现任何构造函数后,编译器将不会提供默认构造函数。

类型 默认值
numeric(int long 等) 0
bool false
char \0
enum 0
reference null
初始化语句

可以在初始化语句中初始化成员变量的值,而不需要都在构造函数中进行。

public class Person
{
    // 下面的初始化语句中初始化了成员变量的值为 "Unknown"
    private string name = "Unknown";
    public Person(string name)
    {
      this.name = name;
    }
}

// 下面的代码编译会出错,提示构造函数没有提供 string 类型的参数
Person p = new Person();
ICloneable 接口

C#中没有复制构造函数。

public class Person: ICloneable
{
    private string name = "Unknown";
    public Person(string name)
    {
        this.name = name;
    }
    // 注意该函数的返回类型为 Object
    public Object Clone()
    {
        Person newObj = new Person (name);
        return newObj;
    }
}
对象构造顺序
public class Temp
{
    public Temp(string arg)
    {
        Console.WriteLine("Temp " + arg);
    }
}
public class TestA
{
    public Temp tempA = new Temp("A");
    public TestA()
    {
        Console.WriteLine("TestA");
        Print();
    }
    public virtual void Print()
    {
        Console.WriteLine("TestA.Print");
    }
};

public class TestB : TestA
{
    Temp tempB = new Temp("B");
    public TestB()
    {
        Console.WriteLine("TestB");
    }
    public override void Print()
    {
        Console.WriteLine("TestB.Print");
    }
};
class Program
{
    static void Main(string[] args)
    {
        TestB b = new TestB();
    }
}
// Output:
// Temp B
// Temp A
// TestA
// TestB.Print
// TestB
// 类自己的初始化语句先执行 然后执行父类初始化语句 然后执行父类构造函数(父类构造函数中多态已经可用) 然后执行子类构造函数
Activator.CreateInstance
using System;

// default constructor
obj = Activator.CreateInstance<T>();
// constructor with args
Type obj_type = typeof(T);
obj = (T)Activator.CreateInstance(obj_type, arg0);

使用静态成员

静态成员

C#中不可以通过对象来访问类的静态成员方法和变量。但是在类内可以直接访问静态成员方法和变量。
CLR 保证在类的其他操作之前运行静态构造方法。静态构造方法通常用于初始化语句无法完成或者仅需一次的设置工作。

静态类

C#中没有全局方法或全局变量、常量。可以创建静态类来封装全局方法和全局变量、常量。
静态类无法实例化,静态类不能被派生,静态类不可含非静态成员。

public static class AppConstant
{
    public static int MaxPlayer = 3;
}
AppConstant.MaxPlayer = 4;
Console.WriteLine(AppConstant.MaxPlayer);

销毁对象

C#提供了垃圾回收器,因此不需要显式地销毁对象。但如果对象要控制非托管的资源,用完后显式地释放还是需要的。

析构函数

对非托管资源的隐式控制是通过析构方法来提供的,它会在对象销毁时有垃圾回收器调用。

~MyClass(){}
// 上面的代码会被编译器翻译为
protected override void Finalize()
{
	try
  {}
  finally
  {
		base.Finalize();
  }
}
Dispose 方法

显式调用析构方法是不合法的。如果需要处理昂贵的非托管资源,需要尽快关闭和清除他们,应该实现 IDisposable 接口。
IDisosable 接口要求实现者定义一个名为 Dispose()的方法,清除我们认为重要的一切。
如果提供了 Dispose()方法,应该停止垃圾回收器调用对象的析构方法,这样保证只进行一次资源释放。

  public class TestDispose:IDisposable
  {
      public static void RunTestDispose (bool isRun)
      {
          if (!isRun)
              return;

          TestDispose temp = new TestDispose ();
          temp.Dispose ();
      }

      bool is_disposed = false;
      public TestDispose ()
      {
      }
      protected virtual void Dispose(bool disposing)
      {
          if(!is_disposed)
          {
              if(disposing)
              {
                  Console.WriteLine("Not in destructor,Ok to reference other objects");
              }
              Console.WriteLine("Disposing ... ");
          }
          is_disposed = true;
      }
      // 此为 IDisposable 定义的方法
      public void Dispose()
      {
          Dispose(true);
          GC.SuppressFinalize(this);
      }
      ~TestDispose()
      {
          Dispose(false);
          Console.WriteLine("In destructor");
      }
  }
using 语句

using 语句用于保证 Dispose 会尽可能最早的时刻调用。

//方法 1
using (Font theFont = new Font("Arial",10.0f))
{
	// 使用 theFont
	// 编译器会调用 theFont 的 Dispose
}
//方法 2
Font anotherFont = new Font("Arial",10.0f);
using (anotherFont)
{
	// 使用 anotherFont
  // 编译器会调用 anotherFont 的 Dispose
}

方法 2 存在风险。
首先:如果在创建对象后,进入 using 语句之前发生异常,对象将不会被清除。
其次:变量在 using 语句块结束后仍然在作用域中,但是其资源已经释放了,所以再次使用它时可能会有错误。

参数传递

默认情况下值类型是按值传递给方法的。
int arg = 10;
void ChangeArgTo20(int arg)
{
    arg = 20;
}
ChangeArgTo20(arg);
Console.WriteLine("arg = " + arg);
// arg = 10
通过 ref 可以实现按照引用传递值类型的参数。
int arg = 10;
void ChangeArgTo20(ref int arg)
{
    arg = 20;
}
// 注意: 传递参数必须标识 ref,否则会报错
ChangeArgTo20(ref arg);
Console.WriteLine("arg = " + arg);
// arg = 20
通过 out 可以克服明确赋值问题。
int arg;
void ChangeArgTo20(out int arg)
{
    arg = 20;
}
// 注意: 传递参数必须标识 out,否则会报错
ChangeArgTo20(out arg);
Console.WriteLine("arg = " + arg);

int arg1;
void ChangeArgAdd10(out int arg1)
{
    // 注意:下面的代码必须有,否则编译报错,提示 out 类型参数必须在函数内赋值
    arg1 = 10;
    arg1 += 10;
}

通过属性封装数据

通过属性,客户代码可以访问类的状态,就像直接访问成员字段一样,而实际上这是通过类方法访问才实现的。
通过 public private protected internal 实现属性访问控制
使用属性要小心,属性本身实际上是成员方法,在初始化所有成员变量之前不能调用成员方法。
只定义 set 时,属性只可写不可读。只定义 get 时,属性只可读不可写。

  public class Person{
      private string name;
      public string Name
      {
          get
          {
              return name;
          }
          set
          {
              name = value;
          }
      }
      private int age;
      public int Age
      {
          protected get
          {
              return age;
          }
          set
          {
              age = value;
          }
      }
      private string tel;
      public string Tel
      {
          set{ tel = value; }
      }
  }

继承和多态

多态

必须显式用关键词 override 标记重新定义了虚方法的方法声明。否则通过基类多态调用该方法时不会调用子类的方法。此时编译器会有警告。
函数前加 new 关键词,可以取消上述编译器的警告,注意此时多态调用依然只会调用基类的方法。

调用基类构造方法

可以通过 base 来调用基类构造方法

  public class Button: Control
  {
      private int id = 0;
      public Button(int id)
      {
          this.id = id;
          DrawWindow();
      }
      public override void DrawWindow()
      {
          Console.WriteLine("Button DrawWindow");
      }
  }

  public class LabelButton:Button
  {
      private string label;
      public LabelButton(int id,string label)
          :base(id)
      {
          this.label = label;
          DrawWindow();
      }
      public override void DrawWindow()
      {
          Console.WriteLine("LabelButton DrawWindow");
      }
  }

  LabelButton tmp = new LabelButton(10, "LabelBtn");
  // 输出: 在基类构造函数中调用虚函数,实际被调用的是派生类的函数
  // LabelButton DrawWindow
  // LabelButton DrawWindow

抽象类

将方法指定为抽象方法可以强制子类实现基类的该抽象方法。
抽象方法没有实现。
使类的一个或多个方法为抽象方法,会使类变为抽象类,而且必须在类定义前加 abatract 修饰符。
抽象类无法实例化。
抽象类代表了一种抽象的理念,要为所有派生类创建一个“合同(contract)”。也就是说,抽象类描述了要实现该抽象的所有类的公共方法。

  // 类内部有抽象方法时,必须将类声明为抽象类
  //public class Control
  abstract public class Control
  {
      private int id = 0;

      public int Id {
          get {
              return id;
          }
          set {
              id = value;
          }
      }

      // 可以将函数指定为 virtual 提供默认实现
      //        public virtual void DrawWindow ()
      //        {
      //        }

      // error 抽象方法不能有实现
      //        abstract public void DrawWindow ()
      //        {
      //        }

      // 可以将函数指定为 abstract 如果子类没有实现 abstract 方法则子类也需要标记为抽象类
      abstract public void DrawWindow ();
  }

  abstract public class ConrolX : Control
  {
      // 不需要重新声明抽象方法,否则会隐藏 Control 中的声明
      //abstract public void DrawWindow ();
  }

  public class Button: Control
  {
      public Button (int id)
      {
          this.Id = id;
      }

      public override void DrawWindow ()
      {
          Console.WriteLine ("Button DrawWindow");
      }
  }

密封类

抽象类是用来派生的,与抽象相对的设计概念是密封。sealed 置于类声明之前用来阻止派生。
struct 是隐式密封的,因此在定义 struct 的时候不用使用 sealed 和 abstract 关键字

public sealed class SealedClass
{
    public int x;
    public int y;
}
public class MyDerivedC: SealedClass {} // Error

万类之根:Object

所有 C#类,无论是何类型,都可以看成是从 System.Object 派生而来的。值类型也包括在内。
类不需要声明从 Ojbect 派生,继承是隐含的。
所有的值类型均隐式派生自 System.ValueType。

类型的装箱和拆箱

装箱和拆箱是使值类型能够被当成引用类型(对象)的处理过程。值被装箱到一个 Object 里然后拆箱回一个值类型。
装箱是一种隐含的转换。
拆箱必须是显式的。

int i = 123;
Object obj = i;   // 装箱
int j = (int)obj; // 拆箱

嵌套类

嵌套类的方法可以访问外层类的私有成员。

  public class OuterClass
  {
      private static int outer_obj_count = 0;
      public OuterClass()
      {
          outer_obj_count++;
      }
      public class NestedClass
      {
          private static int nested_obj_count = 0;
          public void PrintOuterObjCount()
          {
              Console.WriteLine(outer_obj_count);
          }
          public void PrintNestedObjCount()
          {
              Console.WriteLine(nested_obj_count);
          }
      }
  }

操作符重载

c#中,操作符都是一些静态方法,其返回值表示操作结果,其参数是操作数。
C#要求必须成对重载操作符。例如 =与! <与> <=与>=
转换操作符重载中,implicit 关键字用于转换肯定成功,不会丢失信息的时候;否则就用关键字 explicit。

// 详细代码请看 Fraction.cs 文件
public class Fraction
{
    // 省略部分代码 ......
    public static implicit operator Fraction (int theInt)
    {
        return new Fraction (theInt);
    }

    public static explicit operator int (Fraction fraction)
    {
        return fraction.numerator / fraction.denominator;
    }

    public static bool operator== (Fraction lhs, Fraction rhs)
    {
        if (lhs.denominator == rhs.denominator &&
            lhs.numerator == rhs.numerator) {
            return true;
        }
        return lhs.numerator / lhs.denominator == rhs.numerator / rhs.denominator;
    }

    public static bool operator!= (Fraction lhs, Fraction rhs)
    {
        return !(lhs == rhs);
    }
}

结构体

结构体不支持继承和析构函数。结构体是值类型。(所以函数的结构体类型的参数是按值传递的)
结构体数组在内存使用方面效率相对更好。结构体集合的效率就不行了。集合的元素必须是引用类型,所以结构体必须进行装箱处理。
结构体不能通过初始化语句初始化结构体的实例字段。

  public struct Student
  {
      public string name = "hi"; // this is error
      public int age;
  }

结构体成员默认的访问权限也为 private。

定义结构体

[attribue 性质][访问修饰符]struct 标识符[:接口列表]
{结构体成员}

创建结构对象

// 方法 1
Student stu1 = new Student();
// 方法 2
Student stu2;
stu2.name = "";
stu2.age = 0;
// 方法 2 需要手动初始化所有成员的初始值。当结构体内有私有成员变量时,方法 2 将无法使用,因为无法初始化私有成员变量。

接口

接口是向客户保证类或结构体行为方式的一种协定。定义接口时可以定义实现该接口的类需要实现的方法、属性、索引器和事件。
继承抽象类描述了 is-a 的关系,类实现接口描述了 implement 的关系。

定义接口

[attribue 性质][访问修饰符]interface 标识符[:基列表]
{接口主体}
interface 后的标识符通常会以 I 开头,不过不是必须的。
基列表列出了此接口扩展的接口。
接口中属性的声明并没有实现 get\set 方法。
接口中的方法声明没有访问修饰符,接口的方法隐含就是 public 的,因为接口是要其他类使用的协定。

  public interface IStorable
  {
      void Read ();

      void Write (Object obj);

      int Status {
          get;
          set;
      }
  }

  public class GDoc : IStorable
  {
      public void Read ()
      {
          Console.WriteLine ("GDoc read ...");
      }

      public void Write (Object obj)
      {
          Console.WriteLine ("GDoc write ...");
      }

      private int status;

      public int Status {
          get;
          set;
      }
  }

类可以实现多个接口

public class GDoc : IStorable, ICompressible
{ ... }

扩展接口

public interface ILoggedCompressible : ICompressible
{
    void LogSavedBytes();
}

组合接口

public interface IStorableCompressible: IStorable,ICompressible
{
}

转换为接口

  IStorable iObj = doc as IStorable;
  if(iObj != null)
   {
       iObj.Read();
   }

is 和 as 操作符

is 可用来判断某个对象是否为某个类型。也可以判断某个对象是否实现了某个接口。
表达式 is 类型 // is 返回 true 或 false
is 运算符只考虑引用转换、装箱转换和取消装箱转换。不考虑其他转换,如用户定义的转换。
as 是将 is 和转换操作结合起来。首先测试转换是否合法,如果是就进行转换。如果转换不合法就返回 null。
表达式 as 类型 // as 返回转换后的对象 或 null
as 用于在兼容的引用类型之间执行转换.as 运算符只执行引用转换和装箱转换。as 运算符无法执行其他转换,如用户定义的转换。

int iValue = 10;
Console.WriteLine ("iValue is object = {0}", iValue is object);
Console.WriteLine ("iValue is int    = {0}", iValue is int);
Console.WriteLine ("iValue as object = {0}", iValue as object);
// output
// iValue is object = True
// iValue is int    = True
// iValue as object = 10

// 下面的代码编译会出错
Console.WriteLine ("iValue as int    = {0}", iValue as int);

接口和抽象类比较

若要创建一个会被许多人使用的类库,最好使用抽象基类;这样当你需要增加一个新的方法时,只需要在抽象基类中添加一个虚方法,然后配一个默认实现就好了。
若只是为单个项目创建类,则使用接口更好,因为接口更加灵活且具有弹性。

重定义接口的实现

实现类可以自由地将任何或全部实现接口的方法标记为虚。派生类可以重定义或提供新的实现。

  public interface IStorable
  {
      void Read ();

      void Write (Object obj);

      int Status {
          get;
          set;
      }
  }

  public class GDoc : IStorable
  {
      public virtual void Read ()
      {
          Console.WriteLine ("GDoc read ...");
      }

      virtual public void Write (Object obj)
      {
          Console.WriteLine ("GDoc write ...");
      }

      private int status;

      public int Status {
          get;
          set;
      }
  }

  public class GNote : GDoc
  {
      public override void Read ()
      {
          Console.WriteLine ("GNote read ...");
      }

      public override void Write (Object obj)
      {
          Console.WriteLine ("GNote write ...");
      }
  }

显式接口实现

当需要实现的两个接口有相同的方法时,其中一个接口的方法需要显式实现。
显式实现声明的方法不能用访问修饰符,该方法隐含为公共的。
显式实现声明的方法不能用 abstract\virtual\override\new 修饰符声明。
将接口方法通过显式实现,可以一定程度的隐藏接口方法,从而达到有选择公开接口方法的目的。

  public class GDoc : IStorable,ITalk
  {
      public virtual void Read ()
      {
          Console.WriteLine ("GDoc read ...");
      }

      virtual public void Write (Object obj)
      {
          Console.WriteLine ("GDoc write ...");
      }

      public void Talk ()
      {
          Console.WriteLine("IStorable Talk implement");
      }

      void ITalk.Talk ()
      {
          Console.WriteLine("ITalk Talk implement");
      }

      private int status;

      public int Status {
          get;
          set;
      }
  }

隐藏接口成员

  public interface IGBase
  {
      int P {
          get;
          set;
      }
  }

  public interface IGDerived:IGBase
  {
      // new 可以隐藏 IGBase 的 P 成员
      new int P {
          get;
          set;
      }
  }

  public class GMyClass:IGDerived
  {
      int IGBase_P;

      int IGBase.P {
          get {
              return IGBase_P;
          }
          set {
              IGBase_P = value;
          }
      }

      int p = 10;

      public int P {
          get {
              return p;
          }
          set {
              p = value;
          }
      }
  }

访问密封类和结构体

使用值类型实现接口时,一定要通过对象访问接口成员,而不要通过接口引用。因为将值类型对象转换为接口引用时会对值类型对象进行装箱操作,
通过接口引用调用接口方法是在装箱后的引用对象上进行的。

GStudentA stu1 = new GStudentA ("God1"); //GStudentA is a struct
IChangeName iCN = stu1;
iCN.Name = "God2";
Console.WriteLine ("stu1 name = {0}", stu1.Name);
Console.WriteLine ("iCN  name = {0}", iCN.Name);
// output
// stu1 name = God1
// iCN  name = God2

GStudentB stu2 = new GStudentB ("Dog1"); // GStudentB is a sealed class
IChangeName iCN2 = stu2;
iCN2.Name = "Dog2";
Console.WriteLine ("stu2 name = {0}", stu2.Name);
Console.WriteLine ("iCN2 name = {0}", iCN2.Name);
// output
// stu2 name = Dog2
// iCN2 name = Dog2

数组索引器与集合

数组

C#中数组为对象,数组可以有自己的方法和属性。

声明数组

类型[] 数组名;

内存分配

C#数组为引用类型,所以其在堆中分配,数组内的元素如何分配要看他们自己的类型。如果数组元素为值类型,则所有元素在为数组分配的内存块中创建。
如果数组元素为引用类型,分配给数组的内存将用来存放对实际元素的引用。实际元素本身是在堆中分配的,所占内存和分配给数组的内存是不同的。

默认值

创建值类型数组时,每个元素最初都存放着数组所存类型的默认值。
创建引用类型数组时,每个元素被初始化为 null。

遍历数组

foreach(类型 标识符 in 表达式)语句
for(int i=0; i<arr.Length; i++)语句

初始化数组元素
  Employee[] empArr = new Employee[3] {
      new Employee (110),
      new Employee (111),
      new Employee (112)
  };
  Employee[] empArr = {
      new Employee (110),
      new Employee (111),
      new Employee (112)
  };
params 关键字

params 可用于可变数目的数组函数参数。

  public static void PrintIntArray (params int[] intArr)
  {
      foreach (int value in intArr) {
          Console.WriteLine (value);
      }
  }
  int[] intArr = { 11111, 11112, 11113, 11114 };
  PrintIntArray (intArr);
  PrintIntArray (1111, 1112, 1113);
多维数组

数组可分为规则数组和不规则数组。规则数组每行长度是相同的,不规则数组是数组组成的数组。

规则数组

二维数组 类型[,]数组名;
三维数组 类型[,,]数组名;

  int[2,3]arr;     //2 行 3 列的二维数组
  int[2,3,4]arr;   //三维数组
  int[,] arr = {
      {0,1,2},
      {3,4,5},
      {6,7,8},
      {9,10,11}
  };                       //4 行 3 列的二维数组
  arr[1,1];                //访问第二行第二列的元素,该值为 4
不规则数组

类型[][]…数组名;

int[3][]arr;      //二维整型不规则数组
arr[0] = new int[4];
arr[1] = new int[2];
arr[2] = {0,2,4,6,8};
arr[2][3];        //访问第三行第四列的元素,该值为 8
数组转换

如果维数相同,且引用元素类型可以转换,那么可以进行数组间的转换。如果元素类型可以隐式转换,则可以进行隐式转换,否则必须进行显式转换。
Tips: 值类型元素的数组不可以转换。

ImgButton[] imgBtnArr = { new ImgButton (10, "10"), new ImgButton (11, "11") };
PrintControl (imgBtnArr);
Control[] tmpCtrlArr = imgBtnArr;
PrintControl (tmpCtrlArr);
ImgButton[] tmpImgBtnArr = (ImgButton[])tmpCtrlArr;
PrintControl (tmpImgBtnArr);

public static void PrintControl (Control[] controlArr)
{
    foreach (Control ctr in controlArr) {
        Console.WriteLine ("controlId = {0}", ctr.Id);
    }
}


float[] floatArr = { 1.0f, 1.1f, 1.2f };
float[] floatArr2 = {0.0f, 1.0f};
// 下面的代码正确
floatArr2 = floatArr;
PrintArray<float> (floatArr2);

double[] doubleArr = { 2.0, 2.1, 2.2 };
// 下面的代码编译会出错
doubleArr = (double[])floatArr;
数组排序

Array.Sort 可用来排序数组。
Array.Reverse 可用来将数组元素顺序反转。

索引器

索引器是一种特殊的属性,可以通过 get set 方法来指定其行为。
(返回)类型 this[(索引)类型 参数]{get;set}
索引类型
索引操作符在 C#中不能重载,所以提供了索引器。

  public class ListBox:IEnumerable<string>
  {
      public ListBox (params string[] init_strs)
      {
          strings = new string[256];
          foreach (string str in init_strs) {
              strings [count++] = str;
          }
      }

      public IEnumerator<string> GetEnumerator ()
      {
          foreach (string s in strings) {
              yield return s;
          }
      }

      IEnumerator IEnumerable.GetEnumerator ()
      {
          return GetEnumerator ();
      }

      public void Add (string item)
      {
          if (count >= strings.Length) {
              //
          } else {
              strings [count++] = item;
          }
      }

      public string this [int index] {
          get {
              if (index >= count) {
                  return "";
              } else {
                  return strings [index];
              }
          }
          set {
              if (index >= strings.Length) {
                  //
              } else {
                  if (index >= count) {
                      count = index + 1;
                  }
                  strings [index] = value;
              }
          }
      }

      public string this [string index] {
          get {
              int idx = findString (index);
              if (idx != -1) {
                  return strings [idx];
              } else {
                  return "";
              }
          }
          set {
              int idx = findString (index);
              if (idx != -1) {
                  strings [idx] = value;
              } else {
                  //
              }
          }
      }

      private int findString (string str)
      {
          for (int i = 0; i < count; i++) {
              if (strings [i].StartsWith (str)) {
                  return i;
              }
          }
          return -1;
      }

      public int Count {
          get {
              return count;
          }
          //private set;
      }

      private string[] strings;
      private int count;
  }

集合接口

接口 目的
ICollection<T> 泛型集合的基接口
IEnumerator<T> IEnumerable<T> 用 foreach 语句枚举集合
ICollection<T> 所有集合都要实现,以提供 CopyTo()方法,以及 Count、IsSynchronized 和 SyncRoot 属性
IComparer<T> IComparable<T> 比较集合中的两个对象以对集合排序
IList<T> 用于数组可索引的集合
IDictionary<K,V> 用于基于键值对的集合,如 Dictionary
约束

通过关键字 where 指定约束。
public class Node<T>:IComparable<Node<T>> where T : IComparable<T> / 指定约束 T 需要实现 IComparable<T>接口
public class Node<T> where T : new() /
指定 T 支持不带参数的构造函数
public class Node<T> where T : class / 指定 T 可以被赋值为 null
public class ComMgr<ComType> where ComType : Component /
指定 ComType 为 Component 类型或 Component 的派生类

下面链接中给出了各种 where 指定的约束示例:

  public class Node<T>:IComparable<Node<T>> where T : IComparable<T>
  {
      private T data;
      private Node<T> prev;
      private Node<T> next;

      public Node (T data)
      {
          this.data = data;
      }

      public T Data{ get { return data; } }

      public Node<T> Next { get { return next; } }

      public int CompareTo (Node<T> rhs)
      {
          return data.CompareTo (rhs.Data);
      }

      public bool Equals (Node<T> rhs)
      {
          return data.Equals (rhs.Data);
      }

      public Node<T> Add (Node<T> newNode)
      {
          if (this.CompareTo (newNode) > 0) {
              newNode.next = this;
              if (this.prev != null) {
                  this.prev.next = newNode;
                  newNode.prev = this.prev;
              }
              this.prev = newNode;

              return newNode;
          } else {
              if (this.next != null) {
                  this.next.Add (newNode);
              } else {
                  this.next = newNode;
                  newNode.prev = this;
              }
              return this;
          }
      }

      public override string ToString ()
      {
          string output = data.ToString ();
          if (next != null) {
              output += ", "    + next.ToString ();
          }
          return output;
      }
  }

  public class LinkedList<T> where T : IComparable<T>
  {
      private Node<T> headNode = null;

      public T this [int index] {
          get {
              int count = 0;
              Node<T> node = headNode;
              while (node != null && count <= index) {
                  if (count == index) {
                      return node.Data;
                  } else {
                      count++;
                      node = node.Next;
                  }
              }
              throw new ArgumentOutOfRangeException ();
          }
      }

      public void Add (T data)
      {
          if (headNode == null) {
              headNode = new Node<T> (data);
          } else {
              headNode.Add (new Node<T> (data));
          }
      }

      public override string ToString ()
      {
          if (headNode == null) {
              return string.Empty;
          } else {
              return this.headNode.ToString ();
          }
      }
  }
实现 IComparer IComparable
  // IComparable    定义的接口方法 int CompareTo(object obj)
  // IComparable<T> 定义的接口方法 int CompareTo(T other);
  public class Employee:IComparable<Employee>
  {
      private int empID;
      private int yearsOfSvr = 1;

      public int EmpID {
          get{ return empID; }
          set{ empID = value; }
      }

      public int YearsOfSvr {
          get{ return yearsOfSvr; }
          set{ yearsOfSvr = value; }
      }

      public Employee (int empID)
      {
          this.empID = empID;
      }

      public Employee (int empID, int yearsOfSvr)
      {
          this.empID = empID;
          this.yearsOfSvr = yearsOfSvr;
      }

      public static EmployeeComparer GetComparer ()
      {
          return new EmployeeComparer ();
      }

      public int CompareTo (Employee rhs)
      {
          return this.empID.CompareTo (rhs.EmpID);
      }

      public int CompareTo (Employee rhs, EmployeeComparer.ComparerType cmpType)
      {
          switch (cmpType) {
              case EmployeeComparer.ComparerType.EmpID:
                  return this.empID.CompareTo (rhs.EmpID);
              case EmployeeComparer.ComparerType.YearOfSvr:
                  return this.yearsOfSvr.CompareTo (rhs.yearsOfSvr);
          }
          return 0;
      }

      public override string ToString ()
      {
          return string.Format ("EmpID={0}, SvrYears={1}", empID, yearsOfSvr);
      }

      public class EmployeeComparer:IComparer<Employee>
      {
          public enum ComparerType
          {
              EmpID,
              YearOfSvr
          }

          private ComparerType compType;

          public ComparerType CompType {
              get{ return compType; }
              set{ compType = value; }
          }

          public bool Equals (Employee lhs, Employee rhs)
          {
              return lhs.CompareTo (rhs) == 0;
          }

          public int GetHashCode (Employee e)
          {
              return e.GetHashCode ();
          }

          public int Compare (Employee lhs, Employee rhs)
          {
              return lhs.CompareTo (rhs, compType);
          }
      }
  }
实现 IEnumerable<T>

由于 IEnumerable<T>扩展(继承)了旧的 IEnumerable 接口,所以实现 IEnumerable<T>时,要实现两个不同的方法:
IEnumerator<T> GetEnumerator();
IEnumerator GetEnumerator(); // 由于和泛型版本的方法同名,所以该方法的实现需要使用显式接口实现

//IEnumerator    定义的接口方法为 IEnumerator GetEnumerator()
//IEnumerator<T> 定义的接口方法为 IEnumerator<T> GetEnumerator();

public IEnumerator<T> GetEnumerator ()
{
    bool isUseType1 = false;
    if (isUseType1) {
        for (GListNode<T> iter = first; iter != null; iter = iter.Next) {
            yield return iter.Value;
        }
    }
    else {
        GListNode<T> iter = first;
        while (iter != null) {
            yield return iter.Value;
            iter = iter.Next;
        }
    }
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
{
    return GetEnumerator ();
}

字符串与正则表达式

string

C#中 string 是一种正常的类型,而不是字符数组。string 为引用类型。
string 类的声明为:

public sealed class String:IComparable<T>,ICloneable,IConvertible,IEnumerable<T>

每个 string 对象都是一个不变的 unicode 字符序列。默认情况下每个字符都采用 UTF16 编码,string 不变这一事实意味着看似要改变字符串的方法实际上返回的是一个修改后的副本,原字符串在内存中是不变的,直至被垃圾回收。这可能会带来性能问题,所以如果需要频繁修改字符串,应该使用 StringBuilder。

public static void ChangeStr (string str)
{
    str = "after changed\n";
}
string str = "before changed\n";
ChangeStr(str);
Console.WriteLine (str); // 输出: before changed

//修改 string
string line = "My name is God!";
char[] lineCharArr = line.ToCharArray ();
lineCharArr [0] = 'm';
line = new string (lineCharArr);

// @ 符号会告知字符串构造函数忽略转义符和分行符。因此,以下两个字符串是完全相同的:
string p1 = "\\\\My Documents\\My Files\\";
string p2 = @"\\My Documents\My Files\";

// string.Format
Debug.Log(string.Format("{0:D2}", 1));                 // 01
Debug.Log(string.Format("{0}", 0.123));                // 0.123
Debug.Log(string.Format("{0:00.00}", 111.123));        // 111.12
string 填充
// str长度不足maxStrLen时,用空格填充str使其长度达到maxStrLen
str.PadRight(maxStrLen, ' ');
str.PadLeft(maxStrLen, ' ');

StringBuilder

System.Text.StringBuilder 的作用是用来创建和修改字符串的。

StringBuilder mutableStr = new StringBuilder ("My name is God!");
mutableStr [0] = 'm';
mutableStr.Append("This is a sentence.");
Console.WriteLine (mutableStr);

TODO 正则表达式

名字空间 System.Text.RegularExpressions 是所以与正则表达式相关的.NET 框架对象的大本营。

正则表达式匹配 ftp list directory detial
  //drwxr-xr-x  3 user00  staff  102 Aug  9 12:00 testdir
  //-rw-r--r--  1 user00 staff   42 Aug  9 12:00 test.txt
  string ftpDirOrFileInfoUnixRegex =
      @"^" +
      @"(?<dir>[\-ld])" +
      @"(?<permission>[\-rwx]{9})" +
      @"\s+" +
      @"(?<filecode>\d+)" +
      @"\s+" +
      @"(?<owner>\w+)" +
      @"\s+" +
      @"(?<group>\w+)" +
      @"\s+" +
      @"(?<size>\d+)" +
      @"\s+" +
      @"(?<month>\w{3})" +
      @"\s+" +
      @"(?<day>\d{1,2})" +
      @"\s+" +
      @"(?<timeyear>[\d:]{4,5})" +
      @"\s+" +
      @"(?<name>(.*))$";

  //07-25-17 03:49PM <DIR> HDX
  //02-08-17  01:24AM 31605 index.html
  string ftpDirOrFileInfoWinRegex =
      @"^" +
      @"(?<month>\d{1,2})-(?<day>\d{1,2})-(?<year>\d{1,2})" +
      @"\s+" +
      @"(?<hour>\d{1,2}):(?<minutes>\d{1,2})(?<ampm>am|pm)" +
      @"\s+" +
      @"(?<dir>[<]dir[>]\s+)?" +
      @"(?<size>\d+\s+)?" +
      @"(?<name>.*)$";

  var split = new Regex(ftpDirOrFileInfoWinRegex, RegexOptions.IgnoreCase).Match(line);
  string dir = split.Groups["dir"].ToString().Trim();
  string filename = split.Groups["name"].ToString();
  Debug.Log("dir = "+ dir + " filename = " + filename);

TODO 异常处理

委托和事件

委托

委托是一种引用类型,用来封装带有特定签名和返回类型的方法。委托可用来封装静态成员方法、实例方法、匿名方法等。

Action
namespace System
{
    public delegate void Action<in T>(T obj);
}
Func
namespace System
{
    public delegate TResult Func<out TResult>();
}

多重委托

委托可以通过+、+=形成多重委托,委托也可以通过-、-=移除多重委托中的委托.

  MultiDelegate.MyClassWithDelegate.StringDelegate writer, logger, transmiter;
  writer = new MultiDelegate.MyClassWithDelegate.StringDelegate (MultiDelegate.MyImplClass.WriteStr);
  logger = new MultiDelegate.MyClassWithDelegate.StringDelegate (MultiDelegate.MyImplClass.LogStr);
  transmiter = new MultiDelegate.MyClassWithDelegate.StringDelegate (MultiDelegate.MyImplClass.TransmitStr);

  writer ("str pass to writer");
  logger ("str pass to logger");

  MultiDelegate.MyClassWithDelegate.StringDelegate multiDelegate = writer + transmiter;
  multiDelegate ("str pass to writer+transmiter");
  multiDelegate += logger;
  multiDelegate ("str pass to writer+transmiter+loger");
  multiDelegate -= transmiter;
  multiDelegate ("str pass to writer+transmiter+loger-transmiter");
  multiDelegate += logger;
  multiDelegate ("str pass to writer+loger+loger");
  ///////////////////////////////////
  //下面为输出
  // Write String     -- str pass to writer
  // Log String       -- str pass to logger
  // Write String     -- str pass to writer+transmiter
  // Transimit String -- str pass to writer+transmiter
  // Write String     -- str pass to writer+transmiter+loger
  // Transimit String -- str pass to writer+transmiter+loger
  // Log String       -- str pass to writer+transmiter+loger
  // Write String     -- str pass to writer+transmiter+loger-transmiter
  // Log String       -- str pass to writer+transmiter+loger-transmiter
  // Write String     -- str pass to writer+loger+loger
  // Log String       -- str pass to writer+loger+loger
  // Log String       -- str pass to writer+loger+loger

事件

可以通过多重委托来实现事件。
event 关键字能够告诉编译器委托只能由定义类调用,其他类只能分别使用相应的+=和-=操作符订阅和退订委托。

事件和接口

事件可以放在接口中,表示需要实现对应委托的 add,remove 方法。

  public interface IDrawingObject
  {
      // Raise this event before drawing
      // the object.
      event EventHandler OnDraw;
  }

  public interface IShape
  {
      // Raise this event after drawing
      // the shape.
      event EventHandler OnDraw;
  }


  // Base class event publisher inherits two
  // interfaces, each with an OnDraw event
  public class Shape : IDrawingObject, IShape
  {
      // Create an event for each interface event
      event EventHandler PreDrawEvent;
      event EventHandler PostDrawEvent;

      object objectLock = new Object ();

      // Explicit interface implementation required.
      // Associate IDrawingObject's event with
      // PreDrawEvent
      event EventHandler IDrawingObject.OnDraw {
          add {
              lock (objectLock) {
                  PreDrawEvent += value;
              }
          }
          remove {
              lock (objectLock) {
                  PreDrawEvent -= value;
              }
          }
      }
      // Explicit interface implementation required.
      // Associate IShape's event with
      // PostDrawEvent
      event EventHandler IShape.OnDraw {
          add {
              lock (objectLock) {
                  PostDrawEvent += value;
              }
          }
          remove {
              lock (objectLock) {
                  PostDrawEvent -= value;
              }
          }


      }

      // For the sake of simplicity this one method
      // implements both interfaces.
      public void Draw ()
      {
          // Raise IDrawingObject's event before the object is drawn.
          EventHandler handler = PreDrawEvent;
          if (handler != null) {
              handler (this, new EventArgs ());
          }
          Console.WriteLine ("Drawing a shape.");

          // RaiseIShape's event after the object is drawn.
          handler = PostDrawEvent;
          if (handler != null) {
              handler (this, new EventArgs ());
          }
      }
  }

异步调用委托

  subDelegate.BeginInvoke (new AsyncCallback (ResultsReturned), subDelegate);
  private void ResultsReturned (IAsyncResult iar)
  {
      DelegateReturnInt subDelegate = (DelegateReturnInt)iar.AsyncState;
      int result = subDelegate.EndInvoke (iar);
      Console.WriteLine ("result = {0}", result);
  }

The CLR and .NET framework

程序集和版本控制

  1. 程序集是一种 Portable Executable 可移植可执行文件。物理上,程序集可以包括一个或多个模块。一个程序集的全部内容会被作为一个部署和重用的单元。一个程序集只会在被调用的时候才会被导入,不被需要时就暂时不会被导入。
  2. 元数据是一种二进制信息,它被存储在程序集中,用来对程序集中的类型和方法进行描述,并提供其他的一些关于程序集的有用信息。
  3. 程序集形成安全边界及类型边界。即一个程序集构成了它其中类型定义的作用域范围,类型定义的作用域不能跨越多个程序集。每一类型的标识均包括该类型所驻留的程序集的名称。
  4. 作为元数据的一部分,每个程序集都有一个清单。它描述着程序集的内容:程序集的标识信息(名称、版本号等),程序集包含的类型和资源列表,程序集包含的模块的列表,描述如何在引用公共类型时映射到包含其声明和实现的代码的信息,以及程序集所依赖的其他程序集的列表。清单就像描述着程序集内容的一份自述地图。

多模块程序集

  1. 一个包含单一模块的程序集只有一个文件,或者是 EXE 或者是 DLL 文件。这个单一模块包含着程序的所有类型说明和实现代码。程序集的清单也内嵌在这个模块中。
  2. 一个多模块程序集可能包含多个文件(零个或一个 EXE 文件及零个或多个 DLL 文件,至少一个 EXE 或 DLL 文件)。程序集清单这时可以作为单独的一个文件出现,也可以嵌在某一个模块中。当多模块程序集被引用的时候,运行环境将会先导入包含程序集清单的文件,然后根据清单导入需要的模块。
  3. 每个模块都有自己的清单,该清单独立于程序集的清单。模块的清单列着这个模块自身对其他程序集的引用。并且任何在这个模块中声明的类型,都会被列在这个和现实代码一道存放的清单中。一个模块还可能包含资源,如一些该模块需要的图片。

共享程序集

共享程序集必须满足一些严格的要求:

  1. 程序集必须有一个强名称。强名称是全局唯一的。任何人都不会生成和你相同的程序集名称,这是由于用一个私钥生成的程序集的名称和用其他私钥生成的程序集名称不相同。
  2. 共享程序集必须防止比它更新的版本被错误地当做这个程序集被引用,因此共享程序集的每个新版本在发布的时候都要带一个新的版本号。
  3. 为了共享该程序集,需要将它放入全局程序集缓存(Global Assembly Cache)中。这是通用语言运行时在文件系统中指定的一块区域,专门用来保存共享程序集。

最终解决 DLL 冲突 - 版本控制

在.NET 编程环境中的共享程序集可以由名称和版本号唯一地标识。GAC 允许同一程序集的不同版本“并行执行”,即同一程序集的较老版本和较新版本在 GAC 中可以同时存在。

性质

性质是一种生成元数据的机制。性质是一个对象,它代表着与你的程序中的一个元素相关的数据。而这个有性质的元素被称为性质的目标元素。

// 下面是一个类或者一个接口的性质,表示目标类在被导出给 COM 时应该继承自 IUnknown 类而不是 IDispatch 类。(类或接口为目标元素)
[NoIDispatch]

// 下面的性质将元数据插入到程序集中,指定该程序的强名称.(程序集为目标元素)
[assembly: AssemblyKeyFile("c:\\myStrongName.key")]

性质目标

性质的目标可以是程序集、类、接口、类成员等等。

名称 用途
All 适用于以下任意中元素:程序集、类、构造函数、委托、枚举、事件、域、接口、方法、模块、参数、特性、返回值或者结构
Assembly 适用于程序集自身
Class 适用于类
Constructor 适用于给定的构造函数
Delegate 适用于委托
Enum 适用于枚举类型
Event 适用于事件
Field 适用于域
Interface 适用于接口
Method 适用于方法
Module 适用于单个模块
Parameter 适用于方法的参数
Property 适用于属性
ReturnValue 适用于返回值
Struct 适用于结构

性质的使用

性质的使用是通过把它们放在方括号里并且紧放在它们的目标元素之前。(目标是程序集的情况除外,在这种情况下需要把它们放在文件的最头部。)
Tips: 必须把程序集属性放在所有 using 语句之后并且在其他任意代码之前。

自定义性质

通过继承 System.Attribute 来实现自定义性质。

  [AttributeUsage (AttributeTargets.Class |
                   AttributeTargets.Constructor |
                   AttributeTargets.Delegate |
                   AttributeTargets.Field |
                   AttributeTargets.Method |
                   AttributeTargets.Property,
                   AllowMultiple = true)]
  public class BugFixAttribute : System.Attribute
  {
      public BugFixAttribute (int bugID, string programmer, string date)
      {
          this.bugID = bugID;
          this.programmer = programmer;
          this.date = date;
      }

      private string comment;

      public string Comment {
          get{ return comment; }
          set{ comment = value; }
      }

      private int bugID;

      public int BugID{ get { return bugID; } }

      private string programmer;

      public string Programmer{ get { return programmer; } }

      private string date;

      public string Date{ get { return date; } }
  }

  // 下面是使用自定义性质的情况
  [BugFix (101, "GuoDong", "2016-07-09")]
  [BugFix (102, "GuoDong", "2016-07-10", Comment = "comment 1")]
  public class MyMath
  {
      public double DoFunc1 (double param)
      {
          return param + this.DoFunc2 (param);
      }

      [BugFix (103, "GuoDong", "2016-07-11", Comment = "comment x")]
      public double DoFunc2 (double param)
      {
          return param / 3;
      }
  }

反射

反射是指一个程序读取其自身的或其他程序的元数据的过程。一个程序被称为在反射它自身或另一个程序,是指该程序提取被反射程序集的元数据用来提交给用户或改变自身程序行为的过程。
在 Reflection 命名空间的类,以及 System.Type 中的类,提供了对元数据进行检查和交互的支持。
反射一般用于以下四种任务:

查看元数据

工具或使用程序可以使用它来显示元数据。

System.Reflection.MemberInfo info = typeof(MyMath);
object[] attri_arr = info.GetCustomAttributes (typeof(BugFixAttribute), false);
foreach (object attri in attri_arr) {
    BugFixAttribute pAttri = (BugFixAttribute)attri;
    Console.WriteLine ("\nBugID:      {0}", pAttri.BugID);
    Console.WriteLine ("Programmer: {0}", pAttri.Programmer);
    Console.WriteLine ("Date:       {0}", pAttri.Date);
    Console.WriteLine ("Comment:    {0}", pAttri.Comment);
}

进行类型发现

这允许你检查程序集中的类型,以及跟这些类型交互或对类型进行实例化。这在创建自定义脚本的时候有用,例如你可能希望允许你的用户使用脚本语言和你的程序
交互。

  Assembly a = Assembly.Load ("mscorlib");
  Type[] types = a.GetTypes ();
  foreach (Type t in types)
  {
       Console.WriteLine ("Type is {0}", t);
  }
  Console.WriteLine ("{0} types found", types.Length);

对方法和特性的迟绑定

这允许程序员可以调用动态实例化的对象的特性和方法,这也称为动态激活。

运行期创建类型(反射输出)

对反射的终极使用是在运行期创建新的类型,然后使用这些类型执行任务。

反射应用实例

反射创建实例
var baseToolT = typeof(GToolBase);
var types = Assembly.GetExecutingAssembly().GetTypes();
foreach(var t in types)
{
    if(!t.IsSubclassOf(baseToolT))
    {
        continue;
    }

    var tool = System.Activator.CreateInstance(t, new object[] { this });
    toolArr.Add(tool as GToolBase);
}
反射序列化泛型 Array List Dictionary
  Type objType = obj.GetType();
  if(objType.FullName.StartsWith("System.Collections.Generic.List"))
  {
      jsonObj = new JSONArray();
      Type[] eType = objType.GetGenericArguments();
      foreach(object temp in obj as IEnumerable)
      {
          jsonObj.Add(SerializeToJSON(temp));
      }
  }
  else if(objType.FullName.StartsWith("System.Collections.Generic.Dictionary"))
  {
      jsonObj = new JSONObject();
      Type[] kvType = objType.GetGenericArguments();
      jsonObj.Add(_JSON_DictKType_Key, kvType[0].FullName);
      jsonObj.Add(_JSON_DictVType_Key, kvType[1].FullName);
      foreach(DictionaryEntry kv in obj as IDictionary)
      {
          jsonObj.Add(kv.Key as string, SerializeToJSON(kv.Value));
      }
  }
  else if(objType.FullName.EndsWith("[]"))
  {
      jsonObj = new JSONObject();
      var arrJsonObj = new JSONArray();
      Type[] eType = objType.GetGenericArguments();
      foreach (object temp in obj as IEnumerable)
      {
          arrJsonObj.Add(SerializeToJSON(temp));
      }
      jsonObj.Add(_JSON_ArrType_Key, arrJsonObj);
  }
  else if(objType.FullName.StartsWith("System"))
  {
      if(objType == typeof(string))
      {
          jsonObj = new JSONString(obj as string);
      }
      else if(objType == typeof(bool))
      {
          jsonObj = new JSONBool((bool)obj);
      }
      else
      {
          jsonObj = new JSONNumber(obj.ToString());
      }
  }
反射创建泛型 Array List Dictionary
  if(jsonObj.Tag == JSONNodeType.Object)
   {
       JSONObject tJsonObj = jsonObj as JSONObject;
       if(tJsonObj[_JSON_DictKType_Key]!=null && tJsonObj[_JSON_DictVType_Key]!=null)
       {
           Type kType = Type.GetType(tJsonObj[_JSON_DictKType_Key]);
           Type vType = Type.GetType(tJsonObj[_JSON_DictVType_Key]);
           var dictType = typeof(Dictionary<,>).MakeGenericType(kType, vType);
           obj = Activator.CreateInstance(dictType);
           var dictAddFunc = dictType.GetMethod("Add", new[] { kType, vType });
           foreach(KeyValuePair<string, JSONNode> kv in tJsonObj)
           {
               if(kv.Key != _JSON_DictKType_Key && kv.Key!=_JSON_DictVType_Key)
               {
                   object dictK = null;
                   if(kType != typeof(string))
                   {
                       dictK = Convert.ChangeType(kv.Value, kType);
                   }
                   else
                   {
                       dictK = kv.Key;
                   }
                   var dictV = DeserializeFromJSON(kv.Value);
                   dictAddFunc.Invoke(obj, new object[] {dictK, dictV});
               }
           }
       }
       else if(tJsonObj[_JSON_CustomType_Key]!=null)
       {
           Type objType = Type.GetType(tJsonObj[_JSON_CustomType_Key]);
           obj = Activator.CreateInstance(objType);
           foreach (KeyValuePair<string, JSONNode> kv in tJsonObj)
           {
               if (kv.Key != _JSON_CustomType_Key)
               {
                   var objV = DeserializeFromJSON(kv.Value);
                   FieldInfo fieldInfo = objType.GetField(kv.Key, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                   if(fieldInfo != null)
                   {
                       objV = Convert.ChangeType(objV, fieldInfo.FieldType);
                       fieldInfo.SetValue(obj, objV);
                   }
                   else
                   {
                       PropertyInfo propInfo = objType.GetProperty(kv.Key, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                       if(propInfo!=null)
                       {
                           propInfo.SetValue(obj, objV, null);
                       }
                       else
                       {
                           Debug.LogError(objType.FullName + "not exist property " + kv.Key);
                       }
                   }
               }
           }
       }
       else if(tJsonObj[_JSON_ArrType_Key]!=null)
       {
           Array arrObj = null;
           JSONArray arrJsonObj = tJsonObj[_JSON_ArrType_Key] as JSONArray;
           int idx = 0;
           foreach(JSONNode v in arrJsonObj)
           {
               var elem = DeserializeFromJSON(v);
               if(arrObj == null)
               {
                   Type elemType = elem.GetType();
                   arrObj = Array.CreateInstance(elemType,arrJsonObj.Count);
               }
               arrObj.SetValue(elem, idx++);
           }
           obj = arrObj;
       }
       else if(tJsonObj[_JSON_ListType_Key]!=null)
       {
           JSONArray arrJsonObj = tJsonObj[_JSON_ListType_Key] as JSONArray;
           MethodInfo listAddFunc = null;
           foreach(JSONNode v in arrJsonObj)
           {
               var elem = DeserializeFromJSON(v);
               if(obj==null)
               {
                   Type elemType = elem.GetType();
                   var listType = typeof(List<>).MakeGenericType(elemType);
                   listAddFunc = listType.GetMethod("Add", new[] {elemType});
                   obj = Activator.CreateInstance(listType);
               }
               listAddFunc.Invoke(obj, new object[] {elem});
           }
       }
   }
反射序列化 其他程序集类
  jsonObj = new JSONObject();
  // 注意:必须使用 AssemblyQualifiedName 无法根据 FullName 获取到其他程序集的类
  jsonObj.Add(_JSON_CustomType_Key, objType.AssemblyQualifiedName);

Tips

static const readonly

static 修饰符可用于类、字段、方法、属性、运算符、事件和构造函数,但不能用于索引器、析构函数或类以外的类型。
const 指定字段或局部变量的值是常数,不能被修改。被指定为 const 的变量,其初始值是在编译期计算的。
readonly 指定字段或局部变量的值为常数,不能被修改。被指定为 readonly 的变量,其初始值是在运行时计算的。
Tips:
static 不支持局部变量,只能用类的静态字段来代替 c++中的局部静态变量。
const 字段只能在该字段的声明中初始化。
readonly 字段可以在声明或构造函数中初始化。因此,根据所使用的构造函数,readonly 字段可能具有不同的值。
const 字段会被当做静态成员变量,所以不必要也不允许为该字段添加 static 修饰符。

数组的初始化器是在运行时执行的,所以无法定义一个 const 数组并用指定的值初始化它。

  class GStaticMember
  {
      public const int const_imember_0 = 10;
      public const int[] int_arr = new int[3]{1,2,3}; // 编译错误提示只能用 null 初始化。
  }
  GStaticMember member = new GStaticMember ();
  //Console.WriteLine ("const_imember_0 = {0}", member.const_imember_0);       // 编译错误,不允许通过对象访问静态字段
  Console.WriteLine ("const_imember_0 = {0}", GStaticMember.const_imember_0);

装箱和拆箱

装箱过程

  1. 在堆中申请内存,内存大小为值类型的大小,再加上额外固定空间(引用类型的标配:TypeHandle 和同步索引块);
  2. 将值类型的字段值(x=1023)拷贝新分配的内存中;
  3. 返回新引用对象的地址(给引用变量 object o)

拆箱过程

  1. 检查实例对象(object o)是否有效,如是否为 null,其装箱的类型与拆箱的类型(int)是否一致,如检测不合法,抛出异常;
  2. 指针返回,就是获取装箱对象(object o)中值类型字段值的地址;
  3. 字段拷贝,把装箱对象(object o)中值类型字段值拷贝到栈上,意思就是创建一个新的值类型变量来存储拆箱后的值;
int iValue = 10;
//Boxing int value
object oValue = iValue;
Console.WriteLine("Unboxing oValue result = " + (int)oValue);
//ERROR 在执行到下面这句代码时,会报错,提示非法的类型转换
float fValue = (float)oValue;
Console.WriteLine("Unboxing oValue result = " + fValue);

优化

使用显式的装箱操作来减少多余的装箱操作
int x = 100;
ArrayList arr = new ArrayList(3);
arr.Add(x);
arr.Add(x);
arr.Add(x);

//上面的代码可以优化为
int x = 100;
ArrayList arr = new ArrayList(3);
object o = x;
arr.Add(o);
arr.Add(o);
arr.Add(o);

使用泛型集合类可以避免装箱操作

override overload overwrite

override 在 C#中实现多态时需要使用 override 关键字来覆盖父类的方法
overload 表示类中同名函数,不同参数,c#重载中不需要关键字,c#中没有 overload 关键字
overwrite 表示重新实现从父类派生而来的方法,需要关键字 new。

GetMethod 无法获取父类中定义的方法

情况 1

// BindingFlags.FlattenHierarchy 表示返回继承而来的方法 - 只返回父类中的 public 和 protected 方法
// BindingFlags.Instance 表示返回实例方法
// BindingFlags.Static 表示返回静态方法
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance;
var deserializeMethod = type.GetMethod("DeserializeTable", flags);

情况 2

检查 DeserializeTable 是否为 public 或 protected 方法

情况 3

函数名相同,参数不同时,获取特定函数需要指定该函数的参数类型。

deserializeMethod = tblMgrType.GetMethod("DeserializeTable", flags, null, new Type[] { typeof(byte[]) }, null);

CSharp Wiki

  • CSharp 发展历史
  • CSharp 特性介绍

https://en.wikipedia.org/wiki/C_Sharp_(programming_language)

HowTo

Base

查看本地 dotnet version

生成 32 位 guid

using System.Text;

private System.Security.Cryptography.SHA1 hash = new System.Security.Cryptography.SHA1CryptoServiceProvider();

private uint GetUInt32HashCode(string strText)
{
    if (string.IsNullOrEmpty(strText)) return 0;

    //Unicode Encode Covering all characterset
    byte[] byteContents   = Encoding.Unicode.GetBytes(strText);
    byte[] hashText       = hash.ComputeHash(byteContents);
    uint   hashCodeStart  = BitConverter.ToUInt32(hashText, 0);
    uint   hashCodeMedium = BitConverter.ToUInt32(hashText, 8);
    uint   hashCodeEnd    = BitConverter.ToUInt32(hashText, 16);
    var    hashCode       = hashCodeStart ^ hashCodeMedium ^ hashCodeEnd;
    return uint.MaxValue - hashCode;
}

Collections

Array List

数组转化为 String
方案 1 string 数组转 string
string[] arr = { "Miami", "Berlin", "Hamburg"};
string s = string.Join(" ", arr);
方案 2 任意类型数组 List 转 string

String.Join<T> Method (String, IEnumerable<T>)

using System.Linq;

int[] array = {1,2,3,4,5};
string ids = String.Join(",", array.Select(p=>p.ToString()).ToArray());
//output  = "1,2,3,4,5";
Sort: 使用指定排序函数进行排序
// x.tex 为 Texture2D
_allObjects.Sort((x, y) => {
    int xSize = x.tex.width * x.tex.height;
    int ySize = y.tex.width * y.tex.height;
    return xSize - ySize;
});

Dictionary

如何用初始化器初始化 Dictionary
  Dictionary<string, int> dic = new Dictionary<string, int> {
      {"name1",3},
      {"name2",3},
      {"name3",3},
      {"name4",3}
  };
  var curries = new[]
      {
          new
          {
              MainIngredient= "Lamb",
              Style= "Dhansak",
              Spiciness= 5
          },
          new
          {
              MainIngredient= "Lamb",
              Style= "Dhansak",
              Spiciness= 5
          },
          new
          {
              MainIngredient= "Chicken",
              Style= "Dhansak",
              Spiciness= 5
          }
      };
Dictionary 如何遍历删除

不能直接遍历 Keys 进行删除,删除元素会修改 Keys 内容,导致 foreach 出错

  // 错误的方法 运行时会抛出异常
  foreach (int key in new List<int>(dict.Keys)) {
      if (key == 1 || key==2) dict.Remove(key);
  }

  // 正确的方法
  List<int> needRemoveKeys = new List<int>();
  foreach (int key in new List<int>(dict.Keys)) {
      if (key == 1 || key==2) needRemoveKeys.Add(key);
  }
  foreach (int key in needRemoveKeys) {
      dict.Remove(key);
  }

HashSet

判断集合相等
  HashSet<string> s1 = new HashSet<string> { "a", "b" };
  HashSet<string> s2 = new HashSet<string> { "b", "a" };
  Debug.Log(string.Format("s1.Equals(s2) = {0}", s1.Equals(s2)));   // false
  Debug.Log(string.Format("Equals(s1, s2) = {0}", Equals(s1, s2))); // false
  Debug.Log(string.Format("s1 == s2 -> {0}", s1==s2));              // false
  if(s1.Count==s2.Count && s1.IsSubsetOf(s2)) // true
  {
      Debug.Log("xxxx");
  }
  if(s1.SetEquals(s2)) // true
  {
      Debug.Log("yyyy");
  }

文件目录操作

目录、文件获取

工作目录 脚本目录
// 获取脚本运行目录
Directory.GetCurrentDirectory();

// 改变当前工作目
Directory.SetCurrentDirectory(curDir + "/../");

// 获取脚本所在目录
//os.path.split(os.path.realpath(__file__))[0]

// 获取路径的标准路径 全路径切其中不含有“./ ../”之类
Path.GetFullPath("./../");

// 获取 a_abs_path 路径相对于 b_abs_path 路径的相对路径
//os.path.relpath(a_abs_path,b_abs_path)

// windows 路径转为 linux 路径
dirPath = Path.GetFullPath(dirPath);
dirPath = dirPath.Replace("\\", "/");

// 获取桌面目录
System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop);
// 获取 HOME 目录
//usr_home = os.path.expanduser(’~')

获取目录名 文件名
using System.IO;
var f = "a/b/c/d.txt"

Console.WriteLine(f.Replace(".txt", ""));                // a/b/c/d
Console.WriteLine(Path.GetDirectoryName(f));             // a/b/c
Console.WriteLine(Path.GetFileName(f));                  // d.txt
Console.WriteLine(Path.GetFileNameWithoutExtension(f));  // d
Console.WriteLine(Path.GetExtension(f));                 // .txt
遍历目录下的文件和目录
string[] fileList = Directory.GetFiles(path, "*",  SearchOption.TopDirectoryOnly);
foreach(var file in fileList)
{
    //
}

var dirList = Directory.GetDirectories(desktop);
foreach(var dir in dirList)
{
    Debug.Log("  -> " + dir);
}
递归遍历目录中的文件
string[] fileList = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
foreach(var file in fileList)
{
    //
}
递归遍历目录中的目录
var dirList = Directory.GetDirectories(desktop, "*", SearchOption.AllDirectories);
foreach(var dir in dirList)
{
    Debug.Log("  -> " + dir);
}
判断目录 文件是否存在
File.Exists(file-path);
Directory.Exists(dir-path);

读写目录 文件

创建目录
var dirInfo = new DirectoryInfo(dir-path); //多层目录
dirInfo.Create();

Directory.Create(path); // 一层目录
读写文件
  using System.IO;
  // 写入文件 1 - 以 Append 方式写入
  using (FileStream fs = File.Open(path, FileMode.Append))
  {
      Byte[] info = new UTF8Encoding(true).GetBytes("This is some test text.");
      fs.Write(info, 0, info.Length);
  }

  // 写入文件 2 - 以 非 Append 方式写入
  File.WriteAllText(path, "This is some test text.", Encoding.UTF8);
  File.WriteAllLines(path, new List<string>{"one line.","two line."}, Encoding.UTF8);

  // 写入文件 2 - 以 Append 方式写入
  File.AppendAllText(path, "This is some test text.", Encoding.UTF8);
  File.AppendAllLines(path, new List<string>{"one line.","two line."}, Encoding.UTF8);

  // 读取文件
  if(File.Exists(path))
  {
      string contentStr1 = File.ReadAllText(path, Encoding.UTF8);     // default is Encoding.UTF8
      string[] contentStr2 = File.ReadAllLines(path, Encoding.UTF8);  // default is Encoding.UTF8
      List<string> contentStr3 = File.ReadLines(path, Encoding.UTF8); // default is Encoding.UTF8
      byte[] contentBytes = File.ReadAllBytes(path);
  }
修改文件编码
  using System.Text;
  using System.IO;
  Encoding old_coding = Encoding.GetEncoding("gb2312");
  string old_content = File.ReadAllText(path, coding);
  byte[] old_bytes = old_coding.GetBytes(old_content);

  Encoding new_coding = Encoding.GetEncoding("utf-8");
  byte[] new_bytes = Encoding.Convert(old_coding, new_coding, old_bytes);
  File.WriteAllBytes(path, new_bytes);
修改文件名称
  using System.IO;
  string dirPath = Path.GetDirectoryName(file_path);
  string fileName = Path.GetFileNameWithoutExtension(file_path);
  string fileExt = Path.GetFileExtension(file_path);

  string new_file_path = string.Format("{0}/{1}/{2}", dirPath, new_file_name, fileExt);
  File.Move(file_path, new_file_path);

  string new_file_path2 = Path.ChangeExtension(file_path, ".prefab");
  File.Move(file_path, new_file_path2);
移动文件和目录
  using System.IO;

  Directory.Move(old_file_or_dir_path, new_file_or_dir_path);
复制文件和目录
using System.IO;

public static void CopyDirectory(string srcPath, string desPath)
{
    try
    {
        if (!Directory.Exists(desPath))
        {
            DirectoryInfo desDirInfo = new DirectoryInfo(desPath);
            desDirInfo.Create();
        }
        DirectoryInfo dirInfo = new DirectoryInfo(srcPath);
        FileSystemInfo[] fileSysInfos = dirInfo.GetFileSystemInfos();
        foreach(var fileSysInfo in fileSysInfos)
        {
            var subDesPath = desPath + "/" + fileSysInfo.Name;
            if(fileSysInfo is DirectoryInfo)
            {
                if(!Directory.Exists(subDesPath))
                {
                    Directory.CreateDirectory(subDesPath);
                }
                CopyDirectory(fileSysInfo.FullName, subDesPath);
            }
            else
            {
                File.Copy(fileSysInfo.FullName, subDesPath, true);
            }
        }
    }
    catch(Exception e)
    {
        Console.WriteLine(e);
        throw;
    }
}
删除文件和目录
  using System.IO;

  File.Delete(file_path);           // 删除文件
  Directory.Delete(empty_dir);      // 删除空目录

  Directory.Delete(root_dir, true); // 递归删除目录下的所有文件和目录

创建 C# DLL 工程

参考下面链接内容

DLL 混淆加密

参考资料