《CLR via C#》设计类型.属性、事件

10.属性

属性本质上是方法,每个属性都只是类型中定义的一对方法和一些元数据。get和set访问器都是以内联的方式直接将代码编译到调用它的方法中,性能没损失。

10.1无参属性

类型的字段尽量别公开,否则很容易因为不恰当地使用字段而破坏对象的状态,例如以下代码破坏了一个Employee对象:e.Age = -5;,所以,强烈建议将所有字段都设为private,利用属性来公开私有字段。属性举例:

private int mAge;
public int Age{
    get { return mAge; }
    set{
        if (value < 0)
            Console.WriteLine("error");
        else
            mAge = value;
    }
}

注意:属性不能重载,即不能定义名称相同、类型不同的两个属性;属性不能作为out或ref参数传给方法。

自动实现的属性(简称AIP)

只是单纯地为了封装一个私有字段:public String Name { get; set; },私有字段自动生成并不显示。
注意:如果想手动初始化AIP,就只有在构造器中显示初始化:public Program(String mName) { Name = mName;};AIP需同时有get和set,否则就AIP没意义了;不能显示实现get或set的其中一个,而让另一个自动实现。

对象初始化语法

构造一个对象并设置对象的一些公共属性的简化写法:Employee e = new Employee() { Name = "jump", Age = 23 };等价于Employee e = new Employee(); e.Name = "jump"; e.Age = 23;.如果调用的是Employee 的无参构造器,那么还可以去掉花括号前的小括号。

集合初始化语法

如果属性的类型是List这种,也就是,如果属性的类型实现了IEnumerable或IEnumerable接口,属性就是集合类型。ClassRoom cr = new ClassRoom { StudentNames = { "jump", "chen" } };

public sealed class ClassRoom {
    private List<string> mStudentName = new List<string>();
    public List<string> StudentNames { get { return mStudentName; } } // 不需要实现set
}
匿名类型

一条龙服务:不需要提供类名就可以定义类型,构造实例,初始化属性。var obj = new { Name = "jump", Age = 23 };,但所有属性都是只读的。

10.2有参属性(又叫索引器)

索引器的get至少接收一个参数(this[]里的参数),set至少接收两个参数(this[]里的参数和value)。定义索引器举例:public int this[string mName]{...},调用索引器举例:pp["jump"] = 100;,其中"jump"和this[]里的参数mName对应,100和value对应。

this指向对象,C#只允许在对象上定义索引器,可以像访问数组一样访问对象(必须传一个参数进来,可理解为重载了“[ ]”操作符)。

字典就实现了一个索引器,根据一个键返回与该键关联的值。

11.事件

没啥好讲的,Show the code

// 第一步:定义附加信息的类型
// 这些附加信息会发给事件接收者,附加信息类应继承自System.EventArgs,类名以EventArgs结尾
// 如果没有需要发送给事件接收者的附加信息,就没必要定义这个类,直接用EventArgs.Empty
internal sealed class NewMsgEventArgs : EventArgs 
{
    private readonly string mFrom;
    private readonly string mTo;
    private readonly string mContent;
    internal NewMsgEventArgs(string from, string to, string content) 
    {
        mFrom = from;
        mTo = to;
        mContent = content;
    }
    public string From { get { return mFrom; } }
    public string To { get { return mTo; } }
    public string Content { get { return mContent; } }
}

internal class MsgManager // 可被继承的
{
    // 第二步:在MsgManager类里定义事件成员(一个委托字段),System.EventHandler是委托类型,它的定义:
    // public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
    // 限制了接收者类型里的接收方法的形式为:
    // void MethodName(Object sender, NewMsgEventArgs e){ } sender:事件源 e:包含事件数据的对象
    public event EventHandler<NewMsgEventArgs> NewMsg; // Bell类的构造函数里就引用了这个NewMsg
    
    // 第三步:定义方法来调用接收者类型里的接收方法
    protected virtual void OnNewMsgComing(NewMsgEventArgs e) // 可被子类重写
    {
        // 为了线程安全,将对委托字段的引用复制到临时字段中,Volatile.Read读取委托字段NewMsg的引用
        EventHandler<NewMsgEventArgs> temp = Volatile.Read(ref NewMsg); // temp的参数=接收方法的参数
        if (null != temp)
            temp(this, e); // 调用委托列表里的所有委托的方法,该例是通过调用接收者类型的构造函数来添加委托
    }

    // 第四步:将附加信息构建成对象e,并将e传给第三步的OnNewMsgComing方法
    public void SimulateNewMsg(string from, string to, string content)
    {
        NewMsgEventArgs e = new NewMsgEventArgs(from, to, content);
        OnNewMsgComing(e);
    }

	// 被模拟触发类Simulate里的Main函数调用
    public static void Go()
    {
        MsgManager mm = new MsgManager();
        Bell bell = new Bell(mm);
        mm.SimulateNewMsg("jump", "bill", "I can do it!");
        bell.Unregister(mm);
        mm.SimulateNewMsg("bill", "jump", "Come on!");

        //若没有附加信息,就不用调用mm.SimulateNewMail,像下面这样写就好了,也就没必要有SimulateNewMsg函数了
        //EventArgs e = EventArgs.Empty;
        //mm.OnNewMsgComing(e);
    }
}

// 接收者类型Bell
internal sealed class Bell// 当来了一条消息,就要响铃,并显示消息来自谁,送给谁,具体内容
{
    public Bell(MsgManager mm)
    {
        mm.NewMsg += StartBell; // 给事件NewMsg添加委托,也就是在委托列表里添加委托
    }

    // 第二步的注释里有 接收者类型(Bell)里的接收方法(StartBell)的形式
    private void StartBell(Object sender, NewMsgEventArgs e) 
    {
        Console.WriteLine($"----StartBell----from:{e.From},to:{e.To},content:{e.Content}");
    }

    public void Unregister(MsgManager mm)
    {
        mm.NewMsg -= StartBell; // 给事件NewMsg删除委托,也就是在委托列表里删除委托
    }
}

// 模拟触发类,将附加信息传给第四步的SimulateNewMsg方法
internal class Simulate
{
    static void Main()
    {
        MsgManager.Go();

        Console.ReadLine();
    }
}

补充点1:上述代码使用了编译器隐式实现的事件:public event EventHandler<NewMsgEventArgs> NewMsg;,还可以这样显示实现事件(并不是真正的显示,相对显示吧),代码如下:

// 定义一个私有委托字段(引用类型),该字段指向委托列表头部
private EventHandler<NewMsgEventArgs> mNewMsg;
// 手动定义事件成员
public event EventHandler<NewMsgEventArgs> NewMsg
{
    add { mNewMsg += value; } // 给委托列表添加委托
    remove { mNewMsg -= value; }
}

EventHandler<NewMsgEventArgs> temp = Volatile.Read(ref NewMsg);也要改为EventHandler<NewMsgEventArgs> temp = Volatile.Read(ref mNewMsg);

补充点2:可以使用扩展方法替换OnNewMsgComing()里的所有代码

// 添加扩展方法
public static class EventArgsExtensions
{
    public static void Raise<TEvenArgs>(TEvenArgs e, Object sender,
        ref EventHandler<TEvenArgs> delegateFiled)
    {
        EventHandler<TEvenArgs> temp = Volatile.Read(ref delegateFiled);
        if (null != temp) { temp(sender, e); }
    }
}

再用e.Raise(this, ref mNewMsg);替换OnNewMsgComing()里的所有代码

猜你喜欢

转载自blog.csdn.net/BillCYJ/article/details/90672732
今日推荐