程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#基礎概念二十五問

C#基礎概念二十五問

編輯:關於C#

1.靜態成員和非靜態成員的區別?

2.const 和 static readonly 區別?

3.extern 是什麼意思?

4.abstract 是什麼意思?

5.internal 修飾符起什麼作用?

6.sealed 修飾符是干什麼的?

7.override 和 overload 的區別?

8.什麼是索引指示器?

9.new 修飾符是起什麼作用?

10.this 關鍵字的含義?

11.可以使用抽象函數重寫基類中的虛函數嗎?

12.密封類可以有虛函數嗎?

13.什麼是屬性訪問器?

14.abstract 可以和 virtual 一起使用嗎?可以和 override 一起使用嗎?

15.接口可以包含哪些成員?

16.類和結構的區別?

17.接口的多繼承會帶來哪些問題?

18.抽象類和接口的區別?

19.別名指示符是什麼?

20.如何手工釋放資源?

21.P/Invoke是什麼?

22.StringBuilder 和 String 的區別?

23.explicit 和 implicit 的含義?

24.params 有什麼用?

25.什麼是反射?

以下是我做的一份參考答案(C# 語言范疇之內),如果有不准確、不全面的,歡迎各位朋友指正!

1.靜態成員和非靜態成員的區別?

答:

靜態變量使用 static 修飾符進行聲明,在類被實例化時創建,通過類進行訪問

不帶有 static 修飾符聲明的變量稱做非靜態變量,在對象被實例化時創建,通過對象進行訪問

一個類的所有實例的同一靜態變量都是同一個值,同一個類的不同實例的同一非靜態變量可以是不同的值

靜態函數的實現裡不能使用非靜態成員,如非靜態變量、非靜態函數等

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example01
{
  class Program
  {
    class Class1
    {
      public static String staticStr = "Class";
      public String notstaticStr = "Obj";
    }
    static void Main(string[] args)
    {
      //靜態變量通過類進行訪問,該類所有實例的同一靜態變量都是同一個值
      Console.WriteLine("Class1's staticStr: {0}", Class1.staticStr);
      Class1 tmpObj1 = new Class1();
      tmpObj1.notstaticStr = "tmpObj1";
      Class1 tmpObj2 = new Class1();
      tmpObj2.notstaticStr = "tmpObj2";
      //非靜態變量通過對象進行訪問,不同對象的同一非靜態變量可以有不同的值
      Console.WriteLine("tmpObj1's notstaticStr: {0}", tmpObj1.notstaticStr);
      Console.WriteLine("tmpObj2's notstaticStr: {0}", tmpObj2.notstaticStr);
      Console.ReadLine();
    }
  }
}

結果:

Class1's staticStr: Class

tmpObj1's notstaticStr: tmpObj1

tmpObj2's notstaticStr: tmpObj2

2.const 和 static readonly 區別?

答:

const

用 const 修飾符聲明的成員叫常量,是在編譯期初始化並嵌入到客戶端程序

static readonly

用 static readonly 修飾符聲明的成員依然是變量,只不過具有和常量類似的使用方法:通過類進行訪問、初始化後不可以修改。但與常量不同的是這種變量是在運行期初始化

示例:

測試類:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example02Lib
{
  public class Class1
  {
    public const String strConst = "Const";
    public static readonly String strStaticReadonly = "StaticReadonly";
    //public const String strConst = "Const Changed";
    //public static readonly String strStaticReadonly = "StaticReadonly Changed";
  }
}
客戶端代碼:
using System;
using System.Collections.Generic;
using System.Text;
using Example02Lib;
namespace Example02
{
  class Program
  {
    static void Main(string[] args)
    {
      //修改Example02中Class1的strConst初始值後,只編譯Example02Lib項目
      //然後到資源管理器裡把新編譯的Example02Lib.dll拷貝Example02.exe所在的目錄,執行Example02.exe
      //切不可在IDE裡直接調試運行因為這會重新編譯整個解決方案!!
      //可以看到strConst的輸出沒有改變,而strStaticReadonly的輸出已經改變
      //表明Const變量是在編譯期初始化並嵌入到客戶端程序,而StaticReadonly是在運行時初始化的
      Console.WriteLine("strConst : {0}", Class1.strConst);
      Console.WriteLine("strStaticReadonly : {0}", Class1.strStaticReadonly);
      Console.ReadLine();
    }
  }
}

結果:

strConst : Const

strStaticReadonly : StaticReadonly

修改後的示例:

測試類:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example02Lib
{
  public class Class1
  {
    //public const String strConst = "Const";
    //public static readonly String strStaticReadonly = "StaticReadonly";
    public const String strConst = "Const Changed";
    public static readonly String strStaticReadonly = "StaticReadonly Changed";
  }
}

結果

strConst : Const

strStaticReadonly : StaticReadonly Changed

3.extern 是什麼意思?

答:

extern 修飾符用於聲明由程序集外部實現的成員函數

經常用於系統API函數的調用(通過 DllImport )。注意,和DllImport一起使用時要加上 static 修飾符

也可以用於對於同一程序集不同版本組件的調用(用 extern 聲明別名)

不能與 abstract 修飾符同時使用

示例:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace Example03
{
  class Program
  {
    //注意DllImport是一個Attribute Property,在System.Runtime.InteropServices命名空間中定義
    //extern與DllImport一起使用時必須再加上一個static修飾符
    [DllImport("User32.dll")]
    public static extern int MessageBox(int Handle, string Message, string Caption, int Type);
    static int Main()
    {
      string myString;
      Console.Write("Enter your message: ");
      myString = Console.ReadLine();
      return MessageBox(0, myString, "My Message Box", 0);
    }
  }
}

結果:

4.abstract 是什麼意思?

答:

abstract 修飾符可以用於類、方法、屬性、事件和索引指示器(indexer),表示其為抽象成員

abstract 不可以和 static 、virtual 一起使用

聲明為 abstract 成員可以不包括實現代碼,但只要類中還有未實現的抽象成員(即抽象類),那麼它的對象就不能被實例化,通常用於強制繼承類必須實現某一成員

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example04
{
  #region 基類,抽象類
  public abstract class BaseClass
  {
    //抽象屬性,同時具有get和set訪問器表示繼承類必須將該屬性實現為可讀寫
    public abstract String Attribute
    {
      get;
      set;
    }
    //抽象方法,傳入一個字符串參數無返回值
    public abstract void Function(String value);
    //抽象事件,類型為系統預定義的代理(delegate):EventHandler
    public abstract event EventHandler Event;
    //抽象索引指示器,只具有get訪問器表示繼承類必須將該索引指示器實現為只讀
    public abstract Char this[int Index]
    {
      get;
    }
  }
  #endregion
  #region 繼承類
  public class DeriveClass : BaseClass
  {
    private String attribute;
    public override String Attribute
    {
      get
      {
        return attribute;
      }
      set
      {
        attribute = value;
      }
    }
    public override void Function(String value)
    {
      attribute = value;
      if (Event != null)
      {
        Event(this, new EventArgs());
      }
    }
    public override event EventHandler Event;
    public override Char this[int Index]
    {
      get
      {
        return attribute[Index];
      }
    }
  }
  #endregion
  class Program
  {
    static void OnFunction(object sender, EventArgs e)
    {
      for (int i = 0; i < ((DeriveClass)sender).Attribute.Length; i++)
      {
        Console.WriteLine(((DeriveClass)sender)[i]);
      }
    }
    static void Main(string[] args)
    {
      DeriveClass tmpObj = new DeriveClass();
      tmpObj.Attribute = "1234567";
      Console.WriteLine(tmpObj.Attribute);
      //將靜態函數OnFunction與tmpObj對象的Event事件進行關聯
      tmpObj.Event += new EventHandler(OnFunction);
      tmpObj.Function("7654321");
      Console.ReadLine();
    }
  }
}

結果:

1234567

7

6

5

4

3

2

1

5.internal 修飾符起什麼作用?

答:

internal 修飾符可以用於類型或成員,使用該修飾符聲明的類型或成員只能在同一程集內訪問

接口的成員不能使用 internal 修飾符

值得注意的是,如果為 internal 成員加上了 protected 修飾符,這時的訪問級別為 internal或 protected。只是看字面意思容易弄錯,許多人認為 internal protected 應該是“只有同一個程序集中的子類可以訪問”,但其實它表示“同一個程序集中的所有類,以及所有程序集中的子類都可以訪問”

示例

Example05Lib 項目的 Class1

using System;
using System.Collections.Generic;
using System.Text;
namespace Example05Lib
{
  public class Class1
  {
    internal String strInternal = null;
    public String strPublic;
    internal protected String strInternalProtected = null;
  }
}

結果

Example05Lib 項目的 Class2 類可以訪問到 Class1 的 strInternal 成員,當然也可以訪問到 strInternalProtected 成員,因為他們在同一個程序集裡

Example05 項目裡的 Class3 類無法訪問到 Class1 的 strInternal成員,因為它們不在同一個程序集裡。但卻可以訪問到 strInternalProtected 成員,因為 Class3 是 Class1 的繼承類

Example05 項目的 Program 類既無法訪問到 Class1 的strInternal 成員,也無法訪問到 strInternalProtected 成員,因為它們既不在同一個程序集裡也不存在繼承關系

6.sealed 修飾符是干什麼的?

答:

sealed 修飾符表示密封

用於類時,表示該類不能再被繼承,不能和 abstract 同時使用,因為這兩個修飾符在含義上互相排斥

用於方法和屬性時,表示該方法或屬性不能再被重寫,必須和 override 關鍵字一起使用,因為使用 sealed 修飾符的方法或屬性肯定是基類中相應的虛成員

通常用於實現第三方類庫時不想被客戶端繼承,或用於沒有必要再繼承的類以防止濫用繼承造成層次結構體系混亂

恰當的利用 sealed 修飾符也可以提高一定的運行效率,因為不用考慮繼承類會重寫該成員

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example06
{
  class Program
  {
    class A
    {
      public virtual void F()
      {
        Console.WriteLine("A.F");
      }
      public virtual void G()
      {
        Console.WriteLine("A.G");
      }
    }
    class B : A
    {
      public sealed override void F()
      {
        Console.WriteLine("B.F");
      }
      public override void G()
      {
        Console.WriteLine("B.G");
      }
    }
    class C : B
    {
      public override void G()
      {
        Console.WriteLine("C.G");
      }
    }
    static void Main(string[] args)
    {
      new A().F();
      new A().G();
      new B().F();
      new B().G();
      new C().F();
      new C().G();
      Console.ReadLine();
    }
  }
}

結果:

類 B 在繼承類 A 時可以重寫兩個虛函數,如圖所示:

由於類 B 中對 F 方法進行了密封, 類 C 在繼承類 B 時只能重寫一個函數,如圖所示:

控制台輸出結果,類 C 的方法 F 只能是輸出 類B 中對該方法的實現:

A.F

A.G

B.F

B.G

B.F

C.G

7.override 和 overload 的區別?

答:

override 表示重寫,用於繼承類對基類中虛成員的實現

overload 表示重載,用於同一個類中同名方法不同參數(包括類型不同或個數不同)的實現

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example07
{
  class Program
  {
    class BaseClass
    {
      public virtual void F()
      {
        Console.WriteLine("BaseClass.F");
      }
    }
    class DeriveClass : BaseClass
    {
      public override void F()
      {
        base.F();
        Console.WriteLine("DeriveClass.F");
      }
      public void Add(int Left, int Right)
      {
        Console.WriteLine("Add for Int: {0}", Left + Right);
      }
      public void Add(double Left, double Right)
      {
        Console.WriteLine("Add for int: {0}", Left + Right);
      }
    }
    static void Main(string[] args)
    {
      DeriveClass tmpObj = new DeriveClass();
      tmpObj.F();
      tmpObj.Add(1, 2);
      tmpObj.Add(1.1, 2.2);
      Console.ReadLine();
    }
  }
}

結果:

BaseClass.F

DeriveClass.F

Add for Int: 3

Add for int: 3.3

8.什麼是索引指示器?

答:

實現索引指示器(indexer)的類可以象數組那樣使用其實例後的對象,但與數組不同的是索引指示器的參數類型不僅限於int

簡單來說,其本質就是一個含參數屬性

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example08
{
  public class Point
  {
    private double x, y;
    public Point(double X, double Y)
    {
      x = X;
      y = Y;
    }
    //重寫ToString方法方便輸出
    public override string ToString()
    {
      return String.Format("X: {0} , Y: {1}", x, y);
    }
  }
  public class Points
  {
    Point[] points;
    public Points(Point[] Points)
    {
      points = Points;
    }
    public int PointNumber
    {
      get
      {
        return points.Length;
      }
    }  
    //實現索引訪問器
    public Point this[int Index]
    {
      get
      {
        return points[Index];
      }
    }
  }
  //感謝watson hua(http://huazhihao.cnblogs.com/)的指點
  //索引指示器的實質是含參屬性,參數並不只限於int
  class WeatherOfWeek
  {
    public string this[int Index]
    {
      get
      {
        //注意case段使用return直接返回所以不需要break
        switch (Index)
        {
          case 0:
            {
              return "Today is cloudy!";
            }
          case 5:
            {
              return "Today is thundershower!";
            }
          default:
            {
              return "Today is fine!";
            }
        }
      }
    }
    public string this[string Day]
    {
      get
      {
        string TodayWeather = null;
        //switch的標准寫法
        switch (Day)
        {
          case "Sunday":
            {
              TodayWeather = "Today is cloudy!";
              break;
            }
          case "Friday":
            {
              TodayWeather = "Today is thundershower!";
              break;
            }
          default:
            {
              TodayWeather = "Today is fine!";
              break;
            }
        }
        return TodayWeather;
      }
    }
  }
  class Program
  {
    static void Main(string[] args)
    {
      Point[] tmpPoints = new Point[10];
      for (int i = 0; i < tmpPoints.Length; i++)
      {
        tmpPoints[i] = new Point(i, Math.Sin(i));
      }
      Points tmpObj = new Points(tmpPoints);
      for (int i = 0; i < tmpObj.PointNumber; i++)
      {
        Console.WriteLine(tmpObj[i]);
      }
      string[] Week = new string[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Staurday"};
      WeatherOfWeek tmpWeatherOfWeek = new WeatherOfWeek();
      for (int i = 0; i < 6; i++)
      {
        Console.WriteLine(tmpWeatherOfWeek[i]);
      }
      foreach (string tmpDay in Week)
      {
        Console.WriteLine(tmpWeatherOfWeek[tmpDay]);
      }
      Console.ReadLine();
    }
  }
}

結果:

X: 0 , Y: 0

X: 1 , Y: 0.841470984807897

X: 2 , Y: 0.909297426825682

X: 3 , Y: 0.141120008059867

X: 4 , Y: -0.756802495307928

X: 5 , Y: -0.958924274663138

X: 6 , Y: -0.279415498198926

X: 7 , Y: 0.656986598718789

X: 8 , Y: 0.989358246623382

X: 9 , Y: 0.412118485241757

Today is cloudy!

Today is fine!

Today is fine!

Today is fine!

Today is fine!

Today is thundershower!

Today is cloudy!

Today is fine!

Today is fine!

Today is fine!

Today is fine!

Today is thundershower!

Today is fine!

9.new 修飾符是起什麼作用?

答:

new 修飾符與 new 操作符是兩個概念

new 修飾符用於聲明類或類的成員,表示隱藏了基類中同名的成員。而new 操作符用於實例化一個類型

new 修飾符只能用於繼承類,一般用於彌補基類設計的不足

new 修飾符和 override 修飾符不可同時用在一個成員上,因為這兩個修飾符在含義上互相排斥

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example09
{
  class BaseClass
  {
    //基類設計者聲明了一個PI的公共變量,方便進行運算
    public static double PI = 3.1415;
  }
  class DervieClass : BaseClass
  {
    //繼承類發現該變量的值不能滿足運算精度,於是可以通過new修飾符顯式隱藏基類中的聲明
    public new static double PI = 3.1415926;
  }
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine(BaseClass.PI);
      Console.WriteLine(DervieClass.PI);
      Console.ReadLine();
    }
  }
}

結果:

3.1415

3.1415926

10.this 關鍵字的含義?

答:

this 是一個保留字,僅限於構造函數和方法成員中使用

在類的構造函數中出現表示對正在構造的對象本身的引用,在類的方法中出現表示對調用該方法的對象的引用,在結構的構造上函數中出現表示對正在構造的結構的引用,在結構的方法中出現表示對調用該方法的結果的引用

this 保留字不能用於靜態成員的實現裡,因為這時對象或結構並未實例化

在 C# 系統中,this 實際上是一個常量,所以不能使用 this++ 這樣的運算

this 保留字一般用於限定同名的隱藏成員、將對象本身做為參數、聲明索引訪問器、判斷傳入參數的對象是否為本身

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example10
{
  class Class1
  {
    private double c;
    private string value;
    public double C
    {
      get
      {
        return c;
      }
    }
    public Class1(double c)
    {
      //限定同名的隱藏成員
      this.c = c;
    }
    public Class1(Class1 value)
    {
      //用對象本身實例化自己沒有意義
      if (this != value)
      {
        c = value.C;
      }
    }
    public override string ToString()
    {
      //將對象本身做為參數
      return string.Format("{0} Celsius = {1} Fahrenheit", c, UnitTransClass.C2F(this));
    }
    //由於好奇,在這做了一個效率測試,想看看到底哪種方式訪問成員變量更快,結論:區別不大。。。
    public string Test1()
    {
      long vTickCount = Environment.TickCount;
      for (int i = 0; i < 10000000; i++)
        this.value = i.ToString();
      return string.Format("Have this.: {0} MSEL", Environment.TickCount - vTickCount);
    }
    public string Test2()
    {
      long vTickCount = Environment.TickCount;
      for (int i = 0; i < 10000000; i++)
        value = i.ToString();
      return string.Format("Don't have this.: {0} MSEL", Environment.TickCount - vTickCount);
    }
  }
  class UnitTransClass
  {
    public static double C2F(Class1 value)
    {
      //攝氏到華氏的轉換公式
      return 1.8 * value.C + 32;
    }
  }
  class Program
  {
    static void Main(string[] args)
    {
      Class1 tmpObj = new Class1(37.5);
      Console.WriteLine(tmpObj);
      Console.WriteLine(tmpObj.Test1());
      Console.WriteLine(tmpObj.Test2());
      Console.ReadLine();
    }
  }
}

結果:

37.5 Celsius = 99.5 Fahrenheit

Have this.: 4375 MSEL

Don't have this.: 4406 MSEL

11.可以使用抽象函數重寫基類中的虛函數嗎?

答:

可以

需使用 new 修飾符顯式聲明,表示隱藏了基類中該函數的實現

或增加 override 修飾符,表示抽象重寫了基類中該函數的實現

示例:

  class BaseClass
  {
    public virtual void F()
    {
      Console.WriteLine("BaseClass.F");
    }
  }
  abstract class DeriveClass1 : BaseClass
  {
    public abstract new void F();
  }
  //感謝watson hua(http://huazhihao.cnblogs.com/)的指點
  //是他提醒了我還可以用這種方法抽象重寫基類的虛方法
  abstract class DeriveClass2 : BaseClass
  {
    public abstract override void F();
  }

12.密封類可以有虛函數嗎?

答:

可以,基類中的虛函數將隱式的轉化為非虛函數,但密封類本身不能再增加新的虛函數

示例:

  class BaseClass
  {
    public virtual void F()
    {
      Console.WriteLine("BaseClass.F");
    }
  }
  sealed class DeriveClass : BaseClass
  {
    //基類中的虛函數F被隱式的轉化為非虛函數
    //密封類中不能再聲明新的虛函數G
    //public virtual void G()
    //{
    //  Console.WriteLine("DeriveClass.G");
    //}
  }

13.什麼是屬性訪問器?

答:

屬性訪問器(Property Accessor),包括 get 訪問器和 set 訪問器分別用於字段的讀寫操作

其設計目的主要是為了實現面向對象(OO)中的封裝思想。根據該思想,字段最好設為private,一個精巧的類最好不要直接把字段設為公有提供給客戶調用端直接訪問

另外要注意屬性本身並不一定和字段相聯系

14.abstract 可以和 virtual 一起使用嗎?可以和 override 一起使用嗎?

答:

abstract 修飾符不可以和 static、virtual 修飾符一起使用

abstract 修飾符可以和 override 一起使用,參見第11點

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example14
{
  class BaseClass
  {
    public virtual void F()
    {
      Console.WriteLine("BaseClass.F");
    }
  }
  abstract class DeriveClass1 : BaseClass
  {
    //在這裡, abstract是可以和override一起使用的
    public abstract override void F();
  }
  class Program
  {
    static void Main(string[] args)
    {
    }
  }
}

15.接口可以包含哪些成員?

答:

接口可以包含屬性、方法、索引指示器和事件,但不能包含常量、域、操作符、構造函數和析構函數,而且也不能包含任何靜態成員

16.類和結構的區別?

答:

類:

類是引用類型在堆上分配,類的實例進行賦值只是復制了引用,都指向同一段實際對象分配的內存

類有構造和析構函數

類可以繼承和被繼承

結構:

結構是值類型在棧上分配(雖然棧的訪問速度比較堆要快,但棧的資源有限放),結構的賦值將分配產生一個新的對象。

結構沒有構造函數,但可以添加。結構沒有析構函數

結構不可以繼承自另一個結構或被繼承,但和類一樣可以繼承自接口

示例:

根據以上比較,我們可以得出一些輕量級的對象最好使用結構,但數據量大或有復雜處理邏輯對象最好使用類。

如:Geoemtry(GIS 裡的一個概論,在 OGC 標准裡有定義)最好使用類,而 Geometry中點的成員最好使用結構

using System;
using System.Collections.Generic;
using System.Text;
namespace Example16
{
  interface IPoint
  {
    double X
    {
      get;
      set;
    }
    double Y
    {
      get;
      set;
    }
    double Z
    {
      get;
      set;
    }
  }
  //結構也可以從接口繼承
  struct Point: IPoint
  {
    private double x, y, z;
    //結構也可以增加構造函數
    public Point(double X, double Y, double Z)
    {
      this.x = X;
      this.y = Y;
      this.z = Z;
    }
    public double X
    {
      get { return x; }
      set { x = value; }
    }
    public double Y
    {
      get { return x; }
      set { x = value; }
    }
    public double Z
    {
      get { return x; }
      set { x = value; }
    }
  }
  //在此簡化了點狀Geometry的設計,實際產品中還包含Project(坐標變換)等復雜操作
  class PointGeometry
  {
    private Point value;

    public PointGeometry(double X, double Y, double Z)
    {
      value = new Point(X, Y, Z);
    }
    public PointGeometry(Point value)
    {
      //結構的賦值將分配新的內存
      this.value = value;
    }
    public double X
    {
      get { return value.X; }
      set { this.value.X = value; }
    }
    public double Y
    {
      get { return value.Y; }
      set { this.value.Y = value; }
    }
    public double Z
    {
      get { return value.Z; }
      set { this.value.Z = value; }
    }
    public static PointGeometry operator +(PointGeometry Left, PointGeometry Rigth)
    {
      return new PointGeometry(Left.X + Rigth.X, Left.Y + Rigth.Y, Left.Z + Rigth.Z);
    }
    public override string ToString()
    {
      return string.Format("X: {0}, Y: {1}, Z: {2}", value.X, value.Y, value.Z);
    }
  }
  class Program
  {
    static void Main(string[] args)
    {
      Point tmpPoint = new Point(1, 2, 3);
      PointGeometry tmpPG1 = new PointGeometry(tmpPoint);
      PointGeometry tmpPG2 = new PointGeometry(tmpPoint);
      tmpPG2.X = 4;
      tmpPG2.Y = 5;
      tmpPG2.Z = 6;
      //由於結構是值類型,tmpPG1 和 tmpPG2 的坐標並不一樣
      Console.WriteLine(tmpPG1);
      Console.WriteLine(tmpPG2);
      //由於類是引用類型,對tmpPG1坐標修改後影響到了tmpPG3
      PointGeometry tmpPG3 = tmpPG1;
      tmpPG1.X = 7;
      tmpPG1.Y = 8;
      tmpPG1.Z = 9;
      Console.WriteLine(tmpPG1);
      Console.WriteLine(tmpPG3);
      Console.ReadLine();
    }
  }
}

結果:

X: 1, Y: 2, Z: 3

X: 4, Y: 5, Z: 6

X: 7, Y: 8, Z: 9

X: 7, Y: 8, Z: 9

17.接口的多繼承會帶來哪些問題?

答:

C# 中的接口與類不同,可以使用多繼承,即一個子接口可以有多個父接口。但如果兩個父成員具有同名的成員,就產生了二義性(這也正是 C# 中類取消了多繼承的原因之一),這時在實現時最好使用顯式的聲明

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example17
{
  class Program
  {
    //一個完整的接口聲明示例
    interface IExample
    {
      //屬性
      string P
      {
        get;
        set;
      }
      //方法
      string F(int Value);
      //事件
      event EventHandler E;
      //索引指示器
      string this[int Index]
      {
        get;
        set;
      }
    }
    interface IA
    {
      int Count { get; set;}
    }
    interface IB
    {
      int Count();
    }
    //IC接口從IA和IB多重繼承
    interface IC : IA, IB
    {
    }
    class C : IC
    {
      private int count = 100;
      //顯式聲明實現IA接口中的Count屬性
      int IA.Count
      {
        get { return 100; }
        set { count = value; }
      }
      //顯式聲明實現IB接口中的Count方法
      int IB.Count()
      {
        return count * count;
      }
    }
    static void Main(string[] args)
    {
      C tmpObj = new C();
      //調用時也要顯式轉換
      Console.WriteLine("Count property: {0}", ((IA)tmpObj).Count);
      Console.WriteLine("Count function: {0}", ((IB)tmpObj).Count());
      Console.ReadLine();
    }
  }
}

結果:

Count property: 100

Count function: 10000

18.抽象類和接口的區別?

答:

抽象類(abstract class)可以包含功能定義和實現,接口(interface)只能包含功能定義

抽象類是從一系列相關對象中抽象出來的概念, 因此反映的是事物的內部共性;接口是為了滿足外部調用而定義的一個功能約定, 因此反映的是事物的外部特性

分析對象,提煉內部共性形成抽象類,用以表示對象本質,即“是什麼”

為外部提供調用或功能需要擴充時優先使用接口

19.別名指示符是什麼?

答:

通過別名指示符我們可以為某個類型起一個別名

主要用於解決兩個命名空間內有同名類型的沖突或避免使用冗余的命名空間

別名指示符在所有命名空間最外層定義,作用域為整個單元文件。如果定義在某個命名空間內,那麼它只在直接隸屬的命名空間內起作用

示例:

Class1.cs:

using System;
using System.Collections.Generic;
using System.Text;
namespace com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01
{
  class Class1
  {
    public override string ToString()
    {
      return "com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01's Class1";
    }
  }
}

Class2.cs:

using System;
using System.Collections.Generic;
using System.Text;
namespace com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib02
{
  class Class1
  {
    public override string ToString()
    {
      return "com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib02's Class1";
    }
  }
}

主單元(Program.cs):

using System;
using System.Collections.Generic;
using System.Text;
//使用別名指示符解決同名類型的沖突
//在所有命名空間最外層定義,作用域為整個單元文件
using Lib01Class1 = com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01.Class1;
using Lib02Class2 = com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib02.Class1;
namespace Example19
{
  namespace Test1
  {
    //Test1Class1在Test1命名空間內定義,作用域僅在Test1之內
    using Test1Class1 = com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01.Class1;
    class Class1
    {
      //Lib01Class1和Lib02Class2在這可以正常使用
      Lib01Class1 tmpObj1 = new Lib01Class1();
      Lib02Class2 tmpObj2 = new Lib02Class2();
      //TestClass1在這可以正常使用
      Test1Class1 tmpObj3 = new Test1Class1();
    }
  }
  namespace Test2
  {
    using Test1Class2 = com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01.Class1;
    class Program
    {
      static void Main(string[] args)
      {
        //Lib01Class1和Lib02Class2在這可以正常使用
        Lib01Class1 tmpObj1 = new Lib01Class1();
        Lib02Class2 tmpObj2 = new Lib02Class2();
        //注意這裡,TestClass1在這不可以正常使用。
        //因為,在Test2命名空間內不能使用Test1命名空間定義的別名
        //Test1Class1 tmpObj3 = new Test1Class1();

        //TestClass2在這可以正常使用
        Test1Class2 tmpObj3 = new Test1Class2();
        Console.WriteLine(tmpObj1);
        Console.WriteLine(tmpObj2);
        Console.WriteLine(tmpObj3);
        Console.ReadLine();
      }
    }
  }
}

結果:

com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01's Class1

com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib02's Class1

com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01's Class1

20.如何手工釋放資源?

答:

.NET 平台在內存管理方面提供了GC(Garbage Collection),負責自動釋放托管資源和內存回收的工作。但在以下兩種情況需要我們手工進行資源釋放:一、由於它無法對非托管資源進行釋放,所以我們必須自己提供方法來釋放對象內分配的非托管資源,比如你在對象的實現代碼中使用了一個COM對象;二、你的類在運行是會產生大量實例(象 GIS 中的Geometry),必須自己手工釋放這些資源以提高程序的運行效率

最理想的辦法是通過實現一個接口顯式的提供給客戶調用端手工釋放對象,System 命名空間內有一個 IDisposable 接口,拿來做這事非常合適,省得我們自己再聲明一個接口了

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example20
{
  class Program
  {
    class Class1 : IDisposable
    {
      //析構函數,編譯後變成 protected void Finalize(),GC會在回收對象前會調用調用該方法
      ~Class1()
      {
        Dispose(false);
      }
      //通過實現該接口,客戶可以顯式地釋放對象,而不需要等待GC來釋放資源,據說那樣會降低效率
      void IDisposable.Dispose()
      {
        Dispose(true);
      }
      //將釋放非托管資源設計成一個虛函數,提供在繼承類中釋放基類的資源的能力
      protected virtual void ReleaseUnmanageResources()
      {
        //Do something...
      }
      //私有函數用以釋放非托管資源
      private void Dispose(bool disposing)
      {
        ReleaseUnmanageResources();
        //為true時表示是客戶顯式調用了釋放函數,需通知GC不要再調用對象的Finalize方法
        //為false時肯定是GC調用了對象的Finalize方法,所以沒有必要再告訴GC你不要調用我的Finalize方法啦
        if (disposing)
        {
          GC.SuppressFinalize(this);
        }
      }
    }
    static void Main(string[] args)
    {
      //tmpObj1沒有手工釋放資源,就等著GC來慢慢的釋放它吧
      Class1 tmpObj1 = new Class1();
      //tmpObj2調用了Dispose方法,傳說比等著GC來釋放它效率要調一些
      //個人認為是因為要逐個對象的查看其元數據,以確認是否實現了Dispose方法吧
      //當然最重要的是我們可以自己確定釋放的時間以節省內存,優化程序運行效率
      Class1 tmpObj2 = new Class1();
      ((IDisposable)tmpObj2).Dispose();
    }
  }
}

21.P/Invoke是什麼?

答:

在受控代碼與非受控代碼進行交互時會產生一個事務(transition),這通常發生在使用平台調用服務(Platform Invocation Services),即P/Invoke

如調用系統的 API 或與 COM 對象打交道,通過 System.Runtime.InteropServices 命名空間

雖然使用 Interop 非常方便,但據估計每次調用事務都要執行 10 到 40 條指令,算起來開銷也不少,所以我們要盡量少調用事務

如果非用不可,建議本著一次調用執行多個動作,而不是多次調用每次只執行少量動作的原則

22.StringBuilder 和 String 的區別?

答:

String 在進行運算時(如賦值、拼接等)會產生一個新的實例,而 StringBuilder則不會。所以在大量字符串拼接或頻繁對某一字符串進行操作時最好使用 StringBuilder,不要使用 String

另外,對於 String 我們不得不多說幾句:

1.它是引用類型,在堆上分配內存

2.運算時會產生一個新的實例

3.String 對象一旦生成不可改變(Immutable)

3.定義相等運算符(== 和 !=)是為了比較 String 對象(而不是引用)的值

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example22
{
  class Program
  {
    static void Main(string[] args)
    {
      const int cycle = 10000;
      long vTickCount = Environment.TickCount;
      String str = null;
      for (int i = 0; i < cycle; i++)
        str += i.ToString();
      Console.WriteLine("String: {0} MSEL", Environment.TickCount - vTickCount);
      vTickCount = Environment.TickCount;
      //看到這個變量名我就生氣,奇怪為什麼大家都使它呢? :)
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < cycle; i++)
        sb.Append(i);
      Console.WriteLine("StringBuilder: {0} MSEL", Environment.TickCount - vTickCount);
      string tmpStr1 = "A";
      string tmpStr2 = tmpStr1;
      Console.WriteLine(tmpStr1);
      Console.WriteLine(tmpStr2);
      //注意後面的輸出結果,tmpStr1的值改變並未影響到tmpStr2的值
      tmpStr1 = "B";
      Console.WriteLine(tmpStr1);
      Console.WriteLine(tmpStr2);
      Console.ReadLine();
    }
  }
}

結果:

String: 375 MSEL

StringBuilder: 16 MSEL

A

A

B

A

23.explicit 和 implicit 的含義?

答:

explicit 和 implicit 屬於轉換運算符,如用這兩者可以讓我們自定義的類型支持相互交換

explicti 表示顯式轉換,如從 A -> B必須進行強制類型轉換(B = (B)A)

implicit 表示隱式轉換,如從 B -> A 只需直接賦值(A = B)

隱式轉換可以讓我們的代碼看上去更漂亮、更簡潔易懂,所以最好多使用 implicit 運算符。不過!如果對象本身在轉換時會損失一些信息(如精度),那麼我們只能使用 explicit 運算符,以便在編譯期就能警告客戶調用端

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example23
{
  class Program
  {
    //本例靈感來源於大話西游經典台詞“神仙?妖怪?”--主要是我實在想不出什麼好例子了
    class Immortal
    {
      public string name;
      public Immortal(string Name)
      {
        name = Name;
      }
      public static implicit operator Monster(Immortal value)
      {
        return new Monster(value.name + ":神仙變妖怪?偷偷下凡即可。。。");
      }
    }
    class Monster
    {
      public string name;
      public Monster(string Name)
      {
        name = Name;
      }
      public static explicit operator Immortal(Monster value)
      {
        return new Immortal(value.name + ":妖怪想當神仙?再去修煉五百年!");
      }
    }
    static void Main(string[] args)
    {
      Immortal tmpImmortal = new Immortal("紫霞仙子");
      //隱式轉換
      Monster tmpObj1 = tmpImmortal;
      Console.WriteLine(tmpObj1.name);
      Monster tmpMonster = new Monster("孫悟空");
      //顯式轉換
      Immortal tmpObj2 = (Immortal)tmpMonster;
      Console.WriteLine(tmpObj2.name);
      Console.ReadLine();
    }
  }
}

結果:

紫霞仙子:神仙變妖怪?偷偷下凡即可。。。

孫悟空:妖怪想當神仙?再去修煉五百年!

24.params 有什麼用?

答:

params 關鍵字在方法成員的參數列表中使用,為該方法提供了參數個數可變的能力

它在只能出現一次並且不能在其後再有參數定義,之前可以

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
  class App
  {
    //第一個參數必須是整型,但後面的參數個數是可變的。
    //而且由於定的是object數組,所有的數據類型都可以做為參數傳入
    public static void UseParams(int id, params object[] list)
    {
      Console.WriteLine(id);
      for (int i = 0; i < list.Length; i++)
      {
        Console.WriteLine(list[i]);
      }
    }
    static void Main()
    {
      //可變參數部分傳入了三個參數,都是字符串類型
      UseParams(1, "a", "b", "c");
      //可變參數部分傳入了四個參數,分別為字符串、整數、浮點數和雙精度浮點數數組
      UseParams(2, "d", 100, 33.33, new double[] { 1.1, 2.2 });
      Console.ReadLine();
    }
  }
}

結果:

1

a

b

c

2

d

100

33.33

System.Double[]

25.什麼是反射?

答:

反射,Reflection,通過它我們可以在運行時獲得各種信息,如程序集、模塊、類型、字段、屬性、方法和事件

通過對類型動態實例化後,還可以對其執行操作

簡單來說就是用string可以在runtime為所欲為的東西,實際上就是一個.net framework內建的萬能工廠

一般用於插件式框架程序和設計模式的實現,當然反射是一種手段可以充分發揮其能量來完成你想做的任何事情(前面好象見過一位高人用反射調用一個官方類庫中未說明的函數。。。)

示例:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example25Lib
{
  public class Class1
  {
    private string name;
    private int age;
    //如果顯式的聲明了無參數構造函數,客戶端只需要用程序集的CreateInstance即可實例化該類
    //在此特意不實現,以便在客戶調用端體現構造函數的反射實現
    //public Class1()
    //{
    //}
    public Class1(string Name, int Age)
    {
      name = Name;
      age = Age;
    }
    public void ChangeName(string NewName)
    {
      name = NewName;
    }
    public void ChangeAge(int NewAge)
    {
      age = NewAge;
    }
    public override string ToString()
    {
      return string.Format("Name: {0}, Age: {1}", name, age);
    }
  }
}

反射實例化對象並調用其方法,屬性和事件的反射調用略去

using System;
using System.Collections.Generic;
using System.Text;
//注意添加該反射的命名空間
using System.Reflection;
namespace Example25
{
  class Program
  {
    static void Main(string[] args)
    {
      //加載程序集
      Assembly tmpAss = Assembly.LoadFile(AppDomain.CurrentDomain.BaseDirectory + "Example25Lib.dll");
      //遍歷程序集內所有的類型,並實例化
      Type[] tmpTypes = tmpAss.GetTypes();
      foreach (Type tmpType in tmpTypes)
      {
        //獲取第一個類型的構造函數信息
        ConstructorInfo[] tmpConsInfos = tmpType.GetConstructors();
        foreach (ConstructorInfo tmpConsInfo in tmpConsInfos)
        {
          //為構造函數生成調用的參數集合
          ParameterInfo[] tmpParamInfos = tmpConsInfo.GetParameters();
          object[] tmpParams = new object[tmpParamInfos.Length];
          for (int i = 0; i < tmpParamInfos.Length; i++)
          {
            tmpParams[i] = tmpAss.CreateInstance(tmpParamInfos[i].ParameterType.FullName);
            if (tmpParamInfos[i].ParameterType.FullName == "System.String")
            {
              tmpParams[i] = "Clark";
            }
          }
          //實例化對象
          object tmpObj = tmpConsInfo.Invoke(tmpParams);
          Console.WriteLine(tmpObj);
          //獲取所有方法並執行
          foreach (MethodInfo tmpMethod in tmpType.GetMethods())
          {
            //為方法的調用創建參數集合
            tmpParamInfos = tmpMethod.GetParameters();
            tmpParams = new object[tmpParamInfos.Length];
            for (int i = 0; i < tmpParamInfos.Length; i++)
            {
              tmpParams[i] = tmpAss.CreateInstance(tmpParamInfos[i].ParameterType.FullName);
              if (tmpParamInfos[i].ParameterType.FullName == "System.String")
              {
                tmpParams[i] = "Clark Zheng";
              }
              if (tmpParamInfos[i].ParameterType.FullName == "System.Int32")
              {
                tmpParams[i] = 27;
              }
            }
            tmpMethod.Invoke(tmpObj, tmpParams);
          }
          //調用完方法後再次打印對象,比較結果
          Console.WriteLine(tmpObj);
        }
      }
      Console.ReadLine();
    }
  }
}

結果:

Name: Clark, Age: 0

Name: Clark Zheng, Age: 27

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved