.Net边学边讲

.Net边学边讲(一)

www.chinacs.net 2001-6-16 2:28:00 中文C#技术站

经过一段时间的使用,觉得自己对.net的基础知识掌握的不太够,可能许多朋友也有这种感觉,再加上beta2在许多方面进行了调整和更改,因此我想现在用beta1做开发是不太适合,倒不如大家一起探讨一些基础知识、原理,这些东西是不会变的,而且掌握了这些,以后编程会更加得心应手。我这里下的一些类似结论性的文字,不一定对,个人感觉加上看一些文章的体会,希望大家能一起探讨。

1.类型(Type)

类型是.Net的基本单位。.Net中的classes, interfaces, enumerations, structures都是类型,.net中的大多数类型都是类,在.net中即使你写一个再简单的程序(如:hello world)也需要一个类,以winform来说,main函数需要一个类,他是这个类的成员函数。那么类究竟是什么?Dr.Net 上给了一个定义:他是一个创建包含数据和操作这些数据的方法的对象的模版。有点拗口,简单地说,他是一个模版,这个模版中封装了数据和操作这些数据的方法。这样,你创建了一个类就创建了一个新的数据类型、定义了操作这些数据的术语。类型是一些概念的抽象。在一个好的OO设计当中,类型是由一系列的操作定义的,而不是内在的数据格式决定的。而我们一般应将数据声明成私有,这样就避免了程序的其它部分破坏掉这个封装。比如说,我们创建一个类

public class auto{
    private double speed;
    public double Speed{
        get{
            return(speed);
        }
        set{
            speed = value;
        }
    }
}

以这个类为例,我详细的说明以上的观点,类auto可以看成一个新的数据类型,你可以声明其他的成员为这种数据类型:public auto car(){},这样,car返回的是一个auto类型。这跟public int car(){}没什么区别,在.net中int, string...等时类而不仅仅是 其他编程语言中的"数据类型", 实际上C#中的int是.Net Runtime中的Int32类的别名。更深一步说你要是想做一个跨语言的组件,你声明的所有的类型 必须是.Net Runtime的标准类,也就是说用String 代替string,Int32代替int等。那么对于封装,我们如何理解呢?比如说Single 和 Double 他们都代表真实数字的抽象封装。那么数据格式呢?我们往往不需要知道,也不想知道。Single 和 Double有一定的范围和精度,因为他们都是抽象的, 而不是真实的数字。你可以对他们实现一些操作,包括加减乘除。但却不能进行位运算,这是因为这些操作不是封装内的部分,所以他们也不是类型的一部分。类是模版,但我们怎样才能得到真实的对象呢?auto d= new auto();这样我们就明白了,为什么我们在使用类前,为什么要实例化。

.Net边学边讲(二)

www.chinacs.net 2001-6-16 2:29:00 中文C#技术站

我们继续来谈一谈类

与其他面向对象的编程语言不同,.Net中类有四个基本成员,data members(fields), function members(methods), properties, events。其他编程语言只有前两项。这里需注意,data members(fields)永远不要声明成public,因为这样会使使用者无需知道你的类就可以改变你的数据。

public class test{
    private int i;
}

这里i 就是一个data members(fields),需要提及一点,properties提供了方便、安全的数据访问封装。下面来谈谈function members(methods),他有两种存在方式,instance和static。Instance隐含的接受了指向他所在的对象的指针,在C#中你可以用对象名或this得到。如:SomeObject.Method(), 或this.Method()。静态的(Static)Method不能接收到this指针。因此他们不能直接访问类里的任何实例化数据。他的调用方式是SomeClassName.StaticMethod()。他无需例示。function members(methods)默认是private的,即只能在声明他们的类中访问,我们需要声明他们为public以便可以在任何类中访问。function members(methods)可以被重载,也就是说你可以创建多个就有相同名字的方法,比如说test(int i), test(double i), test(), test(string i, bool b).....。.Net编译器会依据你传递的参数决定你在调用哪个方法。当然还有一些其它的修饰符如extern,他的用途是如果你想在.Net Framework中声明你的方法而在.Net Framework之外实现你的方法,比如说在C# 中声明你的方法而在windows本地dll中实现你的方法,你就要在C#中这样声明,public extern yourmethod(){}

接下来我们要谈谈Constructors和Finalize,每个类都至少有一个Constructor的方法,如果你没有提供,C#将自动为你生成一个没有任何参数的Constructor。Constructor是一个与你的类的名字相同且没有任何返回值的的方法。每个Constructor都会调用一个你基类的Constructor(如果没有显性的基类,就调用对象的),这个调用会在你的Constructor主体执行之前调用,这样,你就知道你的基类有没有正确的初始化了。Constructor只在每个对象被创建时调用一次,它的作用是初始化对象的实例,以便于调用。Destructors已不再需要,因为C#会自动提供一个清理对象的方法(叫Finalize)。垃圾自动清理,这也是C#的区别于其他编程语言的特性之一。接下来,我们该谈谈属性了。首先为什么要用属性?来看一个例子,如果你有一个类Person,有一个data members(fields)叫Age 是Int32型的被声明成public,(前面我们讲过不能声明成public,这里就是举例说明为什么不能):

Person Jim = new Person(); // create object; pointed to by Jim
Jim.Age = 23;
Int32 JimsAge = Jim.Age;
Jim.Age = -5; // invalid, but unchecked if you use a field

这段代码会造成两个问题

  1. 你的用户知道了内部数据的细节,他们可能会做一些你不希望发生的操作
  2. 你的用户可能会更改数据为一个不合法的值,如将Age设为零或负数

现在大家可以知道属性的优势了,相对应予上面提的

  1. 用户不会知道你的内部数据结构
  2. 属性方法会保护数据

下面的例程显示了属性的优越。

public class Person
{
    Int32 age; // 注意默认是private
    public Person(Int32 age) { // constructor
        this.age = age; // this消除歧义!
    }
    public Int32 Age { // property
        get {
            return age;
        }
        set { // validating value
            if (value > 0 && value < 150) {
                age = value;
            }
            else { // throw exception if invalid value
                throw new ArgumentException("Age must be between 1 and 150");
            }
        }
    }
}

这段程序有几个值的关注的地方

  1. 在constructor中,我们定义了一个参数也叫age,这会与private age产生歧义。我们用this来消除歧义。
  2. get方法用于读取属性set方法用于存取属性 。关键字value代表属性被设置的值
  3. 在set方法中我们检查了value的值,并在数据不合法时抛出了异常

有点打累了。。。:)

下次我们谈谈event

.Net边学边讲(三)

www.chinacs.net 2001-6-16 2:29:00 中文C#技术站

谈到event,就不能不先说一下callback和delegate

如果你使用过C的话,你应该知道有一个函数叫qsort,是用来给数组排序的。但这个函数显然不能承担广泛意义上的比较,因此你需要传递一个指针,他指向具有比较功能的函数。qsort在每次要比较数组元素时都要调用这个函数。这就是callback的概念,在.Net里也可以实现回调,方法是创建一个接口,实现他,传递一个实现此接口的对象的引用。delegate呢,你可以将他理解成一个安全的函数指针。

Notifications跟callback有点类似,但比简单的回调要复杂的多。callback意味着要调用的callback方法被调用的同时要调用的建立callback的方法。这是一个很紧密的耦合。而Notification则要松散一些,你可以注册将来某段时间会或者不会发生的Notification,只当他们发生时处理,否则不用。

你也许想让你写的组件当一些事情发生时通知其他组件,例如,你想写一个按钮组件,当你Click的时候你可能想通知其他组件,而其他组件将不得不准备向你请求Notification,你就要提供一个方法告诉他们你已经有一个可用的Notification。另一方面,你可能也是当其它组件的一些事情发生时希望被通知的人。这时你就需要找到那个特定组件可以提供什么Notification。

在.Net中event是一个你用来广播、引发、处理Notification的机制。大致是这样的,可以引发事件的组件声明这一事件。而希望处理某一特定组件的某一特定事件的组件通过传递一个方法的delegate向引发事件的组件注册。这样,当事件发生时引发事件的组件就会调用每个已注册的方法。通过delegate和event我们可以实现异步调用的功能。在C#中是这样声明一个代理的:public delegate void LogHandler(String message);代理在处理这种回调时已经是很强大了。但是当我们需要代理被存储以便以后的Notification,就有一点麻烦了。比如说我们有一个对象Button,有一个Click事件。我们可以声明一个ClickHandler 的代理类型用于处理Click事件,在我们的Button的Class中声明一个ClickHandler的public实例,这样其他组件希望Click发生时被通知,就可以简单的把他的代理加到Click代理中去。myButton.Click += new ClickHandler(MyMethod);

看上去着好像没什么问题。但是这里却存在一个大问题,我们声明Click代理是public,这违反了我们以前说过的data fields永远不要声明成public,这会有一系列麻烦。解决的办法是声明成private或protected,然后用属性解决读写。这样我们可以private声明Click,在写一对public方法去增加一个listener及减少一个listener。当然在.Net中,当你声明一个event时,.Net已经为你做好这一切了。声明一个事件:

class AlarmTimer {
    public event EventHandler Alarm;
    // ...
}

这段代码说明AlarmTimer可以向所有其它对象广播它可以引发一个叫Alarm的事件。Alarm事件用的是EventHandler代理类型。EventHandler:无返回值、接受两个参数(Object:指向事件的发送者,EventArgs:包含关于事件的数据)看一个例子:

class AlarmTimer {
    public event EventHandler Alarm;
    private Timer myTimer;
    public AlarmTimer() {
        myTimer = new Timer();
        myTimer.Tick += new EventHandler(OnTick);
    }
    public void Set(Double seconds) {
        myTimer.Interval = (Int32)(seconds * 1000);
        myTimer.Start();
    }
    protected void OnTick(Object sender, EventArgs e) {
        myTimer.Stop();
        if (Alarm != null) Alarm(this, EventArgs.Empty);
    }
    public void ReEnable() {
        myTimer.Enabled = true;
    }
}

注意AlarmTimer既引发事件(Alarm)又处理事件(Timer中的Tick事件)

static AlarmTimer myAlarm = new AlarmTimer();
public static void TestEvent() {
    myAlarm.Alarm += new EventHandler(TimerEventProcessor);
    myAlarm.Set(2);
    Console.WriteLine("Timer is set; alarm will go off in two seconds");
    Application.Run();
}
//处理事件
private static void TimerEventProcessor(Object myObject, EventArgs myEventArgs) {
    if (MessageBox.Show("Wake up! Continue ringing?", "Count is: " + alarmCounter,
                  MessageBox.YesNo) == DialogResult.Yes) {
        alarmCounter += 1;
        myAlarm.ReEnable();
    }
    else {
        Application.Exit();
    }
}

注意:Object和EventArgs不是必需的参数,只是这是一个好的写法模式而已

关于event还有好多没有说,留着以后慢慢说吧。

Contributors: FHL