C#中类的成员
成员定义
在类定义中,也提供该类中所有成员的定义,包括字段、方法和属性。所有成员都有自己的访问级别,用下面的关键字之一来定义。
public——成员可由任何代码访问
private——成员只能由类中的代码访问(如果没有使用任何关键字,就默认使用这个)
internal——成员只能由定义它的程序集(项目)内部代码访问
protected——成员只能由类和派生类访问。
也可以使用protected internal成员只能由项目中派生类来访问。
定义字段
class MyClass
{
public int MyInt;
}
公共字段用PascalCasing来命名,私有字段用camelCasing来命名。
字段也可以使用关键字readonly,这表示这个字段只能在执行构造函数的过程中赋值,或由初始化赋值语句赋值
class MyClass
{
public readonly int MyInt = 17;
}
可使用static关键字将字段声明为静态字段,例如
class MyClass
{
public static int MyInt;
}
静态字段必须通过定义他们的类来访问,例如MyClass.MyInt,而不是通过这个类的对象的实例来访问,另外可以使用一个const关键字来创建一个常量值,按照定义,const成员也是静态的,所以不需要static修饰符。
定义方法
class MyClass
{
public string GetString() => "Here is a string";
}
注意,如果使用了static关键字,这个方法就只能通过类来访问,不能通过对象实例来访问,也可以在方法定义中使用以下关键字
virtual——方法可以重写
abstract——方法必须在非抽象的派生类中重写
override——方法重写了一个基类方法(如果方法被重写就必须使用该关键字)
extern——方法定义放在其他地方
重写方法的实例如下
public class MyBaseClass
{
public virtual void DoSoming();
{
//Base implementation
}
}
public class MyDerivedClass : MyBaseClass
{
public override void DoSomething()
{
//Derived class implementation,override base implementation
}
}
定义属性
属性的定义方式和字段类似,但包含的内容比较多,属性拥有两个类似于函数的块,一个块用于获取属性的值,另一个块用于设置属性的值
public int MyIntProp
{
get
{
//property get code
}
set
{
//Property set code
}
}
get块必须有一个属性类型的返回值,简单属性一般与私有字段相关联,此时get字段可以直接返回该字段的值。
//Field used by property
private int myInt;
//Property.
public int MyIntProp
{
get { return myInt;}
set { //Property set code. }
}
外部代码不能直接访问这个myInt字段,因为其访问级别是私有的,外部代码必须使用属性来访问该字段。set函数采用类似的方式把一个值赋给字段。这里可以使用value关键字来表示用户提供的属性值
//Field used by property
private int myInt;
//Property.
public int MyIntProp
{
get { return myInt;}
set { myInt = value; }
}
value等于类型与属性相同的一个值,所以如果属性和字段使用相同的类型,就不必考虑数据类型的转换,要为可空整数类型提供一个默认值,可以使用这个由表达式构成的成员函数模式
private int? myInt;
public int? MyIntProp
{
get { return myInt; }
set {myInt = value ?? 0;}
}
这个简单属性只是用来阻止对myInt字段的直接访问在对操作进行更多操作控制时,属性的作用才能真正发挥出来。例如
set
{
if (value >= 0 && value <=10)
myInt = value;
else
throw (new ArguementOutOfRangeException ("MyIntProp"
,value,"MyIntProp must be assigned a value between 0 and 10"));
}
还可以在访问器中设置其可访问性,访问器的可访问性不能高于它所属的属性。
使用方法和属性和字段的例子
class MyClass
{
public readonly string Name;
private int intVal;
public int Val
{
get { return intVal; }
set
{
if (value >= 0 && value <= 10)
intVal = value;
else
throw (new ArgumentOutOfRangeException("Val", value,
"Val must be assigned a value between 0 and 10."));
}
}
public override string ToString() => "Name: " + Name + "\nVal: " + Val;
private MyClass() : this("Default Name") { }
public MyClass(string newName)
{
Name = newName;
intVal = 0;
}
private int myDoubledInt = 5;
public int myDoubledIntProp => (myDoubledInt * 2);
}
class Program
{
static void Main(string[] args)
{
WriteLine("Creating object myObj...");
MyClass myObj = new MyClass("My Object");
WriteLine("myObj created.");
for (int i = -1; i <= 0; i++)
{
try
{
WriteLine($"\nAttempting to assign {i} to myObj.Val...");
myObj.Val = i;//使用属性的set访问器
WriteLine($"Value {myObj.Val} assigned to myObj.Val.");//使用属性的get访问器
}
catch (Exception e)
{
WriteLine($"Exception {e.GetType().FullName} thrown.");
WriteLine($"Message:\n\"{e.Message}\"");
}
}
WriteLine("\nOutputting myObj.ToString()...");
WriteLine(myObj.ToString());
WriteLine("myObj.ToString() Output.");
WriteLine("\nmyDoubledIntProp = 5...");
WriteLine($"Getting myDoubledIntProp of 5 is {myObj.myDoubledIntProp}");
ReadKey();
}
}
元组析构
要实现元组析构,只要给支持该特性的任何类欠添加Deconstruct()函数即可,如下面的类所示
public class Location
{
public Location(double latitude,double longitude)=>(Latitude,Longitude) = (latitude,longitude);
public double Latitude { get;}
public double Longitude {get;}
public void Deconstruct(out double latitude,out double longitude)
=>(latitude,longitude) = (Latitude,Longitude);
}
重构成员
添加属性时有一项很方便的技术,可以直接从字段中生成属性,“重构”表示用工具修改代码,而不是手动改
如果MyClass类包含如下字段
public string mystring;
右击该字段,选择Quick Action and Refactorings…就会修改代码为
public string MyString
{
get=>myString;
set=>myString = value;
}
private string myString;
自动属性
使用下列代码可以添加一个自动属性
public int MyIntProp{get;set;}
使用自动属性时,只能通过属性访问数据,不能通过底层的私有字段来访问数据,因为我们不知道底层的私有字段的名称(该名称是在编译期间定义的),不变数据类型可以使用只有get的自动属性
public int MyIntProp{get;}
自动属性的初始化功能由以下声明字段的方式实现
public int MyIntProp{get;}=9;
类成员的其他主题
隐藏基类方法
public class MyBaseClass
{
public void DoSomething()
{
//Base implementation
}
}
public class MyDerivedClass : MyBaseClass
{
public void DoSomething()
{
//Derived class implementation,hides base implementation
}
}
这段代码可以正常运行,但他会生成一个警告,说明隐藏了一个基类成员。,如果确实要隐藏该成员,就可以使用new关键字显示的表明意图
public class MyDerivedClass : MyBaseClass
{
new public void DoSomething()
{
//Derived class implementation,hides base implementation
}
}
上面两种工作方式是完全相同的,但第二种不会显示警告。,注意区别隐藏基类成员和重写他们的区别。考虑下面代码
public class MyBaseClass
{
public virtual void DoSomething() => Write("Base imp");
}
public class MyDerivedClass : MyBaseClass
{
public override void DoSomething() => Write("Derived imp");
}
重写方法将替换基类中的实现代码,这样基类也将使用重写后的方法。,隐藏基类成员对于基类来说还是使用基类的方法。
接口的实现
interface IMyInterface
{
// Interface members.
}
接口成员的定义与类成员的定义相似,但有以下几个重要的区别:
1、不允许使用访问修饰符,所有的接口成员都是隐式公共的
2、接口成员不能包含代码体
3、接口不能定义字段成员
4、不能使用关键字static,virtual,abstract,或sealed来定义接口对象
5、类型定义成员是禁止的
但要隐藏从基接口中继承的成员,可以使用关键字new来定义它们。
interface IMyInterface
{
void DoSomething();
}
interface IMyDerivedInterface : IMyInterface
{
new void DoSomething();
}
在类中实现接口
实现接口的类必须包含该接口所有成员的实现代码,且必须匹配指定的签名(包括匹配指定的get,和set块),并且必须是公共的。例如
public interface IMyInterface
{
void DoSomething();
void DoSomethingElse();
}
public class MyClass : IMyInterface
{
public void DoSomething() {}
public void DoSomethingElse() {}
}
可以显式实现接口也可以隐式实现接口,隐式实现接口,接口和类都可直接访问,显式则只能使用接口来访问。
部分类定义
使用部分类定义,把类的定义放在多个文件中,可将字段、属性和构造函数放在一个文件中,而把方法放在另一个文件中,为此,包含部分类定义的每个文件中对类的使用partial关键字即可,如下所示:
public partial class MyClass { ... }