C# 编程指南
对象(C# 编程指南)
http://msdn2.microsoft.com/zh-cn/library/ms173110.aspx
类或结构定义的作用类似于蓝图,指定该类型可以进行哪些操作。从本质上说,对象是按照此蓝图分配和配置的内存块。对象也称为实例,可以存储在命名变量中,也可以存储在数组或集合中。使用这些变量来调用对象方法及访问对象公共属性的代码称为客户端代码。在 C# 等面向对象的语言中,典型的程序由动态交互的多个对象组成。
一、结构实例与类实例
由于类是引用类型,因此类对象的变量引用该对象在托管堆上的地址。如果将同一类型的第二个对象分配给第一个对象,则两个变量都引用该地址的对象。
public class MyClass
{
//Properties, methods, events...
}
class Program
{
MyClass mc1 = new MyClass();
// mc and mc2 point to the same object.
MyClass mc2 = mc1;
// mc3 is not the same object as mc1 and mc2.
MyClass mc3 = new MyClass();
}
由于结构是值类型,因此结构对象的变量具有整个对象的副本。结构的实例也可以使用 new 运算符来创建,但这不是必需的,如下面的示例所示:
public struct Person
{
public string Name;
public int Age;
}
public class MyClass
{
public void DoSomething()
{
// Create a struct instance by using "new".
// Memory is allocated on thread stack.
Person p1 = new Person();
p1.Age = 9;
p1.Name = "Alex";
// Create a struct object without using "new".
Person p2;
p2.Age = 7;
p2.Name = "Spencer";
// Create new Person and assign to existing instance.
// p3 is a separate object. Values of p2.Name and p2.Age
// are copied into p3.
Person p3 = p2;
}
}
无论是否使用 new 运算符,都会在线程堆栈上为 p1 和 p2 分配内存,当 DoSomething 方法超出范围时回收该内存。当对类实例对象的所有引用都超出范围时,为该类实例分配的内存将由公共语言运行库自动回收(垃圾回收)。无法像在 C++ 中那样明确地销毁类对象。
二、象标识与相等的值
在比较两个对象是否相等时,首先必须明确您是想知道两个变量是否表示内存中的同一对象,还是想知道这两个对象的一个或多个字段的值是否相等。如果您要对值进行比较,则必须考虑这两个对象是值类型(结构)的实例,还是引用类型(类、委托、数组)的实例。
·若要确定两个类实例是否引用内存中的同一位置(意味着它们具有相同的标识),可使用静态 ObjectReferenceEquals()()() 方法。(System..::.Object 是所有值类型和引用类型的隐式基类,其中包括用户定义的结构和类。)
·若要确定两个结构实例中的实例字段是否具有相同的值,可使用 ValueTypeEquals()()() 方法。由于所有结构都隐式继承自 System..::.ValueType,因此可以直接在对象上调用该方法,如下面的示例所示:
if(p2.Equals(p1))
Console.WriteLine("p2 and p1 have the same values.");
Equals 的 System..::.ValueType 实现使用反射,因为它必须能够确定任何结构中有哪些字段。在创建您自己的结构时,重写 Equals 方法可以提供针对您的类型的高效求等算法。
·若要确定两个类实例中字段的值是否相等,您可能需要使用 ObjectEquals()()() 方法或 == 运算符。但是,仅当类重写或重载该方法或运算符,以便通过自定义的方式表明该类型对象“相等”的条件时,才应使用它们。类可能还实现 IEquatable<(Of <(T>)>) 接口或 IEqualityComparer<(Of <(T>)>) 接口。这两个接口都提供可用于测试值是否相等的方法。在设计您自己的类重写 Equals 时,请务必遵循 Equals() 和运算符 == 的重写准则(C# 编程指南)和 Object..::.Equals(Object) 中的准则。
=============================================================================================
类(C# 编程指南)
Customer object3 = new Customer();
Customer object4 = object3;
此代码创建了两个对象引用,它们引用同一个对象。因此,通过 object3 对对象所做的任何更改都将反映在随后使用的 object4 中。由于基于类的对象是按引用来引用的,因此类称为引用类型。
类具有以下特点:
·与 C++ 不同,C# 只支持单继承:类只能从一个基类继承实现。
·一个类可以实现多个接口。有关更多信息
·类定义可在不同的源文件之间进行拆分。
·静态类是仅包含静态方法的密封类。
=============================================================================================
Struct(C# 编程指南)
http://msdn2.microsoft.com/zh-cn/library/saxz13w4.aspx
结构是使用 struct 关键字定义的,例如:
public struct PostalAddress
{
// Fields, properties, methods and events go here...
}
结构与类共享大多数相同的语法,但结构比类受到的限制更多:
·在结构声明中,除非字段被声明为 const 或 static,否则无法初始化。
·结构不能声明默认构造函数(没有参数的构造函数)或析构函数。
由于结构的副本由编译器自动创建和销毁,因此不需要使用默认构造函数和析构函数。实际上,编译器通过为所有字段赋予
默认值来实现默认构造函数。结构不能从类或其他结构继承。
结构是值类型。如果从结构创建一个对象并将该对象赋给某个变量,则该变量包含结构的全部值。复制包含结构的变量时,将复制所有数据,对新副本所做的任何修改都不会改变旧副本的数据。由于结构不使用引用,因此结构没有标识;无法区分具有相同数据的两个值类型实例。C# 中的所有值类型都继承自 ValueType,后者继承自 Object。
结构具有以下特点:
·结构是值类型,而类是引用类型。
·与类不同,结构的实例化可以不使用 new 运算符。
·结构可以声明构造函数,但它们必须带参数。
·一个结构不能从另一个结构或类继承,而且不能作为一个类的基。所有结构都直接继承自 System.ValueType,后者继承自 System.Object。
·结构可以实现接口。
·结构可用作可为 null 的类型,因而可向其赋 null 值。
=============================================================================================
继承(C# 编程指南)
http://msdn2.microsoft.com/zh-cn/library/ms173149.aspx
类可以从其他类中继承。这是通过以下方式实现的:在声明类时,在类名称后放置一个冒号,然后在冒号后指定要从中继承的类(即基类)。例如:
public class A
{
public A() { }
}
public class B : A
{
public B() { }
}
新类(即派生类)将获取基类的所有非私有数据和行为以及新类为自己定义的所有其他数据或行为。因此,新类具有两个有效类型:新类的类型和它继承的类的类型。
在上面的示例中,类 B 既是有效的 B,又是有效的 A。访问 B 对象时,可以使用强制转换操作将其转换为 A 对象。强制转换不会更改 B 对象,但您的 B 对象视图将限制为 A 的数据和行为。将 B 强制转换为 A 后,可以将该 A 重新强制转换为 B。并非 A 的所有实例都可强制转换为 B,只有实际上是 B 的实例的那些实例才可以强制转换为 B。如果将类 B 作为 B 类型访问,则可以同时获得类 A 和类 B 的数据和行为。对象可以表示多个类型的能力称为多态性。
=============================================================================================
抽象类、密封类及类成员(C# 编程指南)
http://msdn2.microsoft.com/zh-cn/library/ms173150.aspx
使用 abstract 关键字可以创建仅用于继承用途的类和类成员,即定义派生的非抽象类的功能。使用 sealed 关键字可以防止继承以前标记为 virtual 的类或某些类成员。
·抽象类和类成员
可以将类声明为抽象类。方法是在类定义中将关键字 abstract 置于关键字 class 的前面。
抽象类不能实例化。抽象类的用途是提供多个派生类可共享的基类的公共定义。例如,类库可以定义一个作为其多个函数的参数的抽象类,并要求程序员使用该库通过创建派生类来提供自己的类实现。
抽象类也可以定义抽象方法。方法是将关键字 abstract 添加到方法的返回类型的前面。
抽象方法没有实现,所以方法定义后面是分号,而不是常规的方法块。抽象类的派生类必须实现所有抽象方法。当抽象类从基类继承虚方法时,抽象类可以使用抽象方法重写该虚方法。
public class D
{
public virtual void DoWork(int i)
{
// Original implementation.
}
}
public abstract class E : D
{
public abstract override void DoWork(int i);
}
public class F : E
{
public override void DoWork(int i)
{
// New implementation.
}
}
如果将虚方法声明为抽象方法,则它对于从抽象类继承的所有类而言仍然是虚的。继承抽象方法的类无法访问该方法的原始实现。在前面的示例中,类 F 上的 DoWork 无法调用类 D 上的 DoWork。在此情况下,抽象类可以强制派生类为虚方法提供新的方法实现
·密封类和类成员
可以将类声明为密封类。方法是在类定义中将关键字 sealed 置于关键字 class 的前面。例如:
public sealed class D
{
// Class members here.
}
密封类不能用作基类。因此,它也不能是抽象类。密封类主要用于防止派生。由于密封类从不用作基类,所以有些运行时优化可以使对密封类成员的调用略快。
在对基类的虚成员进行重写的派生类上的类成员、方法、字段、属性或事件可以将该成员声明为密封成员。在用于以后的派生类时,这将取消成员的虚效果。方法是在类成员声明中将 sealed 关键字置于 override 关键字的前面。例如:
public class D : C
{
public sealed override void DoWork() { }
}
=========================================================================
多态性(C# 编程指南)
http://msdn2.microsoft.com/zh-cn/library/ms173152.aspx
通过继承,一个类可以用作多种类型:可以用作它自己的类型、任何基类型,或者在实现接口时用作任何接口类型。这称为多态性。C# 中的每种类型都是多态的。类型可用作它们自己的类型或用作 Object 实例,因为任何类型都自动将 Object 当作基类型。
多态性不仅对派生类很重要,对基类也很重要。任何情况下,使用基类实际上都可能是在使用已强制转换为基类类型的派生类对象。基类的设计者可以预测到其基类中可能会在派生类中发生更改的方面。例如,表示汽车的基类可能包含这样的行为:当考虑的汽车为小型货车或敞篷汽车时,这些行为将会改变。基类可以将这些类成员标记为虚成员,从而允许表示敞篷汽车和小型货车的派生类重写该行为。
·多态性概述
当派生类从基类继承时,它会获得基类的所有方法、字段、属性和事件。若要更改基类的数据和行为,您有两种选择:可以使用新的派生成员替换基成员,或者可以重写虚拟的基成员。
使用新的派生成员替换基类的成员需要使用 new 关键字。如果基类定义了一个方法、字段或属性,则 new 关键字用于在派生类中创建该方法、字段或属性的新定义。new 关键字放置在要替换的类成员的返回类型之前。例如:
public class BaseClass
{
public void DoWork() { }
public int WorkField;
public int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public new void DoWork() { }
public new int WorkField;
public new int WorkProperty
{
get { return 0; }
}
}
使用 new 关键字时,调用的是新的类成员而不是已被替换的基类成员。这些基类成员称为隐藏成员。如果将派生类的实例强制转换为基类的实例,就仍然可以调用隐藏类成员。例如:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
为了使派生类的实例完全接替来自基类的类成员,基类必须将该成员声明为虚拟的。这是通过在该成员的返回类型之前添加 virtual 关键字来实现的。然后,派生类可以选择使用 override 关键字而不是 new,将基类实现替换为它自己的实现。例如:
public class BaseClass
{
public virtual void DoWork() { }
public virtual int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public override void DoWork() { }
public override int WorkProperty
{
get { return 0; }
}
}
字段不能是虚拟的,只有方法、属性、事件和索引器才可以是虚拟的。当派生类重写某个虚拟成员时,即使该派生类的实例被当作基类的实例访问,也会调用该成员。例如:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Also calls the new method.
可以使用虚方法和属性预先计划未来的扩展。因为在调用虚成员时不考虑调用方正在使用的类型,所以派生类可以选择更改基类的外在行为。
无论在派生类和最初声明虚成员的类之间已声明了多少个类,虚成员都将永远为虚成员。如果类 A 声明了一个虚拟成员,类 B 从 A 派生,类 C 从类 B 派生,则类 C 继承该虚拟成员,并且可以选择重写它,而不管类 B 是否为该成员声明了重写。例如:
public class A
{
public virtual void DoWork() { }
}
public class B : A
{
public override void DoWork() { }
}
public class C : B
{
public override void DoWork() { }
}
派生类可以通过将重写声明为密封的来停止虚拟继承。这需要在类成员声明中将 sealed 关键字放在 override 关键字的前面。例如:
public class C : B
{
public sealed override void DoWork() { }
}
在上面的示例中,方法 DoWork 对从 C 派生的任何类都不再是虚方法,但它仍是 C 的实例的虚方法 -- 即使将这些实例强制转换为类型 B 或类型 A 也是如此。派生类可以通过使用 new 关键字替换密封的方法,如下面的示例所示:
public class D : C
{
public new void DoWork() { }
}
在此情况下,如果在 D 中使用类型为 D 的变量调用 DoWork,被调用的将是新的 DoWork。如果使用类型为 C、B 或 A 的变量访问 D 的实例,对 DoWork 的调用将遵循虚拟继承的规则,即把这些调用传送到类 C 的 DoWork 实现。
已替换或重写某个方法或属性的派生类仍然可以使用基关键字访问基类的该方法或属性。例如:
public class A
{
public virtual void DoWork() { }
}
public class B : A
{
public override void DoWork() { }
}
public class C : B
{
public override void DoWork()
{
// Call DoWork on B to get B's behavior:
base.DoWork();
// DoWork behavior specific to C goes here:
// ...
}
}
多态:替换父方法 1.没有虚方法:再new一下;2.有虚方法:overrid重写方法。