C#基础概念二十五问 【二】 [转]
答:
可以
需使用 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點
示例:
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:
Class2.cs:
主單元(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 對象(而不是引用)的值
示例:
結果:
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
?
示例下載:http://files.cnblogs.com/reonlyrun/CSharp25QExample07.rar
如果你認為還有哪些概念比較重要或容易混淆,可以在回復中提出,我會及時更新這篇隨筆?
一些話:
To: watson hua,謝謝你幫我改正了第4、11、14和19點的錯誤,并且讓我對索引指示器的理解更全面!
To: xiao,謝謝你關于“實例化”的詳細解釋,讓這篇隨筆中的措詞更加精確!
To: charleschen,謝謝你追問,讓第1、第8的提法更恰當!
To: 裝配腦袋,謝謝你提供 internal protected 含義的正確答案!
This posting is provided "AS IS" with no warranties, and confers no rights.
轉載于:https://www.cnblogs.com/RobotTech/archive/2007/08/11/852003.html
總結
以上是生活随笔為你收集整理的C#基础概念二十五问 【二】 [转]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用虚拟机的好处
- 下一篇: Java普通类获取Spring框架Bea