Web开发之C#:(7)C#类型成员
1.类型的成员
类的内部是class下面的花括号范围内的内容:
常量成员:const 修饰的成员,隐式静态常量,只读的,不能修改,用“类名.成员名”实现只读访问。常量的值直接嵌入代码,运行时不用为其分配内存,不能获取常量的地址。
常量的值更改后,若只生成Dll程序集,应用程序不会受到影响,需重新编译应用程序,才能修改生效。如果是可能变化的,static readonly 的字段可解决以上版本更新问题。
静态成员:static修饰的成员,属于类,不属于类的每个实例,用“类名.成员名”实现读写访问。静态成员不属于该类的某一个对象,这个类及所有实例都可以共同访问。
任何对象随时都可以访问这个成员,修改的值会一直保留下去。
实例成员:默认情况非静态、非常量的所有成员都是实例成员。new一个实例出来后,通过这个实例才能调用的实例成员。
数据成员:类或实例的数据。字段。
函数成员:类或实例的操作。属性(特性)、构造函数、方法、索引器、事件、委托等。
静态成员使用场景:
(1)特定功能的方法,而不是对象,使用静态;
(2)程序中大量使用到,写成静态成员;
(3)把静态看作全局的概念!简化工作量,不用new。
2.类型的基础成员
2.1 字段
字段是一种数据成员,容纳了一个值类型实例或者一个引用类型的引用。
字段的直接初始化称为内联初始化,实际也是在构造器中对字段初始化的,内联初始化只是让构造期语法得以简化。
字段一般默认设为私有,提供属性给外界访问,在属性中可以对访问做权限限制。
封装字段生成属性快捷键:Ctrl r,e 然后2次回车
2.2 属性
简单的字段风格的语法来设置或查询类型或对象的部分逻辑状态并保证对象状态不受破坏。
2.3.1属性(特性)
字段需要保护起来,所以设为私有;外界需要访问则通过属性来实现。
(A)属性就是从外界访问字段的渠道。
字段用骆驼(camelCase)命名法,属性用帕斯卡(PascalCase)命名法
(B)属性里面包含两个部分,get,set
这两个部分分别表示:get表示返回字段的值,set表示设置字段的值。
(C)属性可以控制外部赋值的逻辑和返回的值的逻辑。
在类的内部赋值,直接使用字段即可,如果对字段的赋值有约束筛选等判断,使用属性
(D)属性在本质上就是方法,其中get{}是一个方法,set{}是一个方法。
(E)属性不具备存储能力。
2.3.2自动属性
使用自动属性时,虽然没有定义字段,但是系统在后台生成了与属性对应的字段来实现存储的需要:接收赋值以及向外返回值。
针对自动属性的set和get部分,系统也生成了相应的代码来实现功能。
2.3 索引
c#中的索引器
索引器允许类或结构的实例按照与数组相同的方式进行索引。
索引器类似于属性,不同之处在于它们的访问器采用参数。
索引器的声明方式
public 返回类型 this[index类型 index参数]
{
get{ return 返回类型数值;}
set{ 字段数组[index参数]=value;}
}
2.3.1 类的索引
索引:可看作带参数的属性(本质还是方法)
从集合到数组类型的成员,每个对象都是存放很多数据,可以用下标[ ],即中括号的语法访问的。
类的索引示例:
class MyClass
{
public int[] nums = { 1, 2, 4, 56, 78, 90, };
public int this[int index]
{
get { return nums [index]; }
set { nums [index] = value; }
}
}
2.3.2 使用索引操作成员
使用方法:类里面用this加“.”,类外面访问用实例名加“.”。
使用索引:类里面用this加“[]”,类外面访问用实例名加“[]”。
示例:通过实例对象的索引,打印数组的元素
Myclass m = new Myclass();
m[4] = 99;//在m的内部执行了set { nums [4] = 99; }
for (int i = 0; i < m.nums.Length; i )
{
Console.Write("{0} ", m[i]);
}
Console.ReadKey();
2.3.3 索引的重载
class MyClass
{
public int[] nums= { 1, 2, 4, 56, 78, 90, };
//针对nums的索引
public int this[int index]
{
get { return nums[index]; }
set { nums[index] = value; }
}
//针对dic的索引
Dictionary<string, string> dic = new Dictionary<string, string>();
public string this[string name]
{
get { return dic[name]; }
set { dic[name] = value; }
}
//构造方法给字典赋值
public MyClass()
{
dic.Add("张无忌","帅哥");
dic.Add("赵敏", "美女");
}
}
//字典的索引(重载)
m["张无忌"] = "老帅哥";
Console.WriteLine(m["张无忌"]);
2.4 构造方法
2.4.1 类型构造器
用来初始化静态字段的特殊方法。
2.4.2 实例构造器
用来初始化实例字段的特殊方法。
(1)写一个类系统会自动生成(无参的)构造方法。
(2)构造方法是为了在new的时候初始化对象,给对象字段赋初值。
new Myclass()中的“MyClass()”就是在调用构造方法赋值。
(3)手动添加构造方法以后,按需要来赋初值。
手动添加构造方法后,系统自动生成的无参构造方法就不存在了,需手动添加无参构造方法,才不会出错。
(4)构造方法是可以重载的
(5)字段的初始值
初始化时,没有显式赋值的字段的值,数字类型 0,char ‘\0’,bool false,其他null。
2.5 方法
特殊函数,更改或查询一个类或对象的状态。
分部方法
可用于密封类、静态类、值类型。
不想修改工具生成类型的行为。
在分部类中对分部方法进行个性化的实现。
返回类型为void。
不能使用out修饰参数。
扩展方法
三要素:静态类,静态方法, this关键字(后面跟要扩展的类型及参数)
class Program
{
static void Main(string[] args)
{
string email = "zxh@itcast.cn";
Console.WriteLine(email.IsEmail());//为string扩展IsEmail方法
Person p1 = new Person();
p1.Name = "zxh";
p1.SayHi("今天是个好日子");//为Person扩展SayHi方法
Console.ReadKey();
}
}
public class Person
{
public string Name
{
get;
set;
}
public string Email
{
get;
set;
}
}
}
namespace 扩展方法
{
//扩展方法
//1.增加一个静态类
public static class StringExt
{
//2.在静态类中增加一个静态方法
public static bool IsEmail(this string str) //3.用this string str关联要扩展的string类型
{
return Regex.IsMatch(str, @"^\w @\w (\.\w ) $");
}
//第一个参数表示当前的SayHi方法是给Person类型扩展的,
//第二个参数表示是SayHi方法自己的参数
public static void SayHi(this Person per, string msg)
{
//扩展方法只是看起来像Person中的方法,其实根本不是Person类自己的成员,所以在扩展方法中也访问不到类中原来的私有成员
//一般不建议使用扩展方法,主要是微软大量的使用到扩展方法,了解一下。
Console.WriteLine("我叫:{0},我说:{1}", per.Name, msg);
}
}
}
2.6 操作符
操作符重载: 如== 、!=等操作符的重新实现。
转换操作符:隐式或显示将对象从一种类型转换为另一种类型的方法。
2.7 this/base关键字
如果子类成员有和父类成员同名的时候,就必须用this和base区分。
this
表示当前实例,用来访问当前类中定义的成员。
(1)this将当前对象作为参数传递给另一个方法。
(2)this用来调用本类构造方法。
(3)this用作索引器关键字。
(4)this用作隐藏父类同名方法的关键字。
(5)this用作扩展方法的关键字。
(6)如果实例成员中具有与形参相同的名称,使用this关键字可解决这种歧义。还有就是关于形参名和成员变量相同的时候,以就近原则,直接写变量名字表示的就是形参,如果加了this表示的就是这个类的成员。
public void SetName(string FirstName,string LastName)
{
this.FirstName= FirstName;
this.LastName= LastName;
}
base
用于表示父类,通过它可以访问父类的成员。
(1)隐藏父类的情况,父类成员和子类成员不是一回事,这是可用base访问父类成员,this访问子类成员。
(2)重写父类的情况,覆盖和构造函数里面调用父类的方法,可以用base.Method()。
(3)base() 用来调用父类构造方法。
3.类型的高级成员
3.1事件
利用静态事件,一个类型可以向一个或多个静态或实例方法发送通知。
利用实例事件,一个对象可以向一个或者多个静态或实例方法发送通知。
3.1.1 事件的简单示例
我们可以把事件编程简单地分成两个部分:
A).事件发生的类(事件发生器)
B).事件监听和处理的类。
事件发生的类就是说在这个类中触发了一个事件,但这个类并不知道哪个个对象或方法将会接收到并处理它触发的事件。
在事件接收处理的类中,我们需要有一个处理事件的方法。
public class A
{
public delegate void EventHandler(object sender);
public event EventHandler ARun;
public void Run()
{
Console.WriteLine("Trigger an event.");
ARun (this);
}
}
class B
{
//构造函数中得到A的EventHandler委托实例a,并将事件处理方法OnARun添加到a的事件列表中
public B(A a)
{
a. ARun = new A.EventHandler(this. OnARun);
}
private void OnARun(object sender)
{
Console.WriteLine("Received and handled an event." );
Console.Read();
}
}
3.1.2 带事件参数的事件示例
(1) 设计事件参数类型(从EventArgs派生)
EventArgs是包含事件数据的类的基类,此类不包含事件数据,在事件引发时不向事件处理程序传递状态信息的事件会使用此类。
如果事件处理程序需要状态信息,则应用程序必须从此类派生一个类来保存数据。
因为在消息事件中要包含消息的信息,所以要派生一个NewMsgEvenArgs类,来保存新的信息,好让后面知道消息从哪来,发给谁,消息内容是什么。
internal class NewMsgEvenArgs:EventArgs
{
readonly string from,to,msg;
public NewMailEventArgs(string from,string to,string msg)
{
this.from=from;
this.to=to;
this.msg=msg;
}
public string From,To,Msg;
}
(2) 设计事件发生的类
internal calss MsgManager
{
}
在MsgManager中定义事件成员以及引发事件的方法。
A.定义事件成员
定义一个委托类型,指出要调用方法的原型。
使用event关键字和委托类型定义事件,事件成员访问标识为public。
public delegate void EventHandler<T>(Object sender,T e) where T:EventArgs;
合法的方法原型示例 void Method(Object sender,NewMsgEventArgs);
public event EventHanlder<NewMsgEventArgs> NewMsg;
B.定义引发事件的方法来通知事件
类应该定义一个受保护的虚方法,在引发事件时,当前类及其派生类中的代码会调用该方法。
protected virtual void OnNewMsg(NewMsgEventArgs e)
{
if(NewMsg!=null) NewMsg(this,e);//这里会执行注册到该事件的方法
}
派生类还可以重写该虚方法,按照自己需要重新实现。
C. 触发事件
internal calss MsgManager
{
public void SendAMsg(string from,string time,string msg)
{
NewMsgEventArgs e = new NewMsgEventArgs(from,time,msg);
OnNewMsg(e);//调用虚方法,通知事件以发生
}
}
(3) 设计监听事件的类型
在该类型中为事件注册处理程序,进行事件监听和业务处理。
internal calss ListenerA
{
pubic ListenerA(MsgManager MsgMgr )
{
MsgMgr.NewMsg =OnReceiveMsg;
}
void OnReceiveMsg(Object sender,NewMsgEventArgs e)
{
string notify=string.Format(“收到{0}发给{1}的消息,内容:{2}”,e.From,e.To,e.Msg);
MessageBox.Show(notify);
}
}
测试:
public class program
{
static void Main(object[] args)
{
MsgManager msgMgr = new MsgManager ();
ListenerA a = new ListenerA(msgMgr );
msgMgr.SendAMsg(“Tom”,”Lucy”,”Hi,Lucy!”);
}
}
3.1.3 理解事件原理
参照委托理解事件:
委托是表示对具有特定参数列表和返回类型的方法的引用的类型。
委托类型的声明与方法签名相似, 有一个返回值和任意数目任意类型的参数。
委托用于将方法作为参数传递给其他方法,在其他方法中调用委托时,相当于调用委托变量中引用的方法(委托扮演代理人的角色)。
事件相当于一个私有的委托成员,加上公开的Add和Remove两个方法。因此外界只能对事件使用 =和-=进行事件处理程序的注册和注销,不能直接调用事件。
事件相当于外界把想要执行的任务委托给定义事件的类来代理完成。与普通委托不同的是,任务执行的时机和条件由代理方(定义事件的类)决定,委托方无权控制。
下面来看看从委托到事件的演变:
(1) 纯委托类型版“事件”,不安全
public delegate void DeleBark();//声明委托
class Dog
{
public void Bark()
{
if (OnBark!=null)
{
OnBark();//委托的调用,依赖于Bark(),即监听了Bark()
}
}
public DeleBark OnBark;//Dog的委托类型成员--“事件”
}
public的委托类型的“事件”,外界不仅可以“注册”,“注销”,还可以直接调用。
外界能直接调用,不符合监听事件的初衷。
(2) 私有委托类型的“事件”,安全可控
OnBark定义为private 的,外界不可直接访问和调用。
增加公共方法AddOnBark和RemoveBark,让外界为这个委托添加或移除方法。
class Dog
{
public void Bark()
{
if (OnBark != null)
{
OnBark();//委托的调用,依赖于Bark(),即监听了Bark()
}
}
private DelBark OnBark;//Dog的委托类型的私有成员--“事件”
//给“事件”注册方法
public void AddBark(DeleBark dele)
{
OnBark = dele;
}
//给“事件”注销方法
public void RemoveBark(DeleBark dele)
{
OnBark -= dele;
}
}
(3) 真的事件
public的事件相当于:一个私有的委托成员,加上公开的Add和Remove两个方法
// 将事件成员被设置为public的event修饰的委托类型成员。
// 封装Add方法和Remove方法,外界可通过“ =”和“-=”为其注册和注销事件响应方法。
// 由于是event修饰的,只允许内部调用,外界不能调用了。
class Dog
{
public void Bark()
{
if (OnBark != null)
{
OnBark();//事件的触发,依赖于Bark(),即监听了Bark()
}
}
public event DelBark OnBark;//定义事件,用原来语言上再加event关键字修饰
}
}
3.2类型
可以定义嵌套于其中的其他类型的成员(类类型、接口类型或委托类型等)。
4.类和成员的访问修饰符
4.1 类的访问修饰符:缺省时为internal
public:所有程序集可访问
命名空间或编译单元内的类只有public和internal两种修饰符
派生类为public类,其基类为protected,internal, private,abstract
protected表示只能从所在类和所在类派生的子类进行访问
internal:本程序集范围类可访问
private:只能被和private类在同一所在类的类访问
private class ClassA {
private class ClassB { }
}
private class ClassC
{
ClassA ca = new ClassA(); //正确
//ClassA.ClassB cb = new ClassA.ClassB(); //错误
}
abstract:抽象类,表示该类只能作为父类被用于继承,而不能进行对象实例化。抽象类可以包含抽象的成员,但这并非必须。
sealed:表示密闭类(密封) (1)类不允许被继承。(2)方法不允许被重写。
4.2成员的访问修饰符:缺省时为private
public 所有类都可访问(不同项目通过添加引用)
protected 本类与子类访问(继承关系)
internal 本程序集(如本项目中)
protected internal 上述二者只要满足其一,就可访问
private 只有本类可以访问