您的位置:首页 > 其它

委托与事件

2016-01-19 16:03 405 查看
委托

C#委托对应于C++中的函数指针,也就是说我希望使用动态的特定的模块来执行代码,这也就是委托的作用。举一个例子,冒泡排序,我需要动态决定是大到小排序还是小到大排序,如果使用硬编码的办法那就是通过一个标志变量来进行判断决定。代码如下:

<span style="font-size:14px;">        static void BublleSort(bool IsS2B, params int[] data)
{
for (int i = 0; i < data.Length; i++)
{
for (int j = 0; j < i; j++)
{
if (IsS2B)
{
if (data[j] > data[i])
{
data[i] ^= data[j];
data[j] ^= data[i];
data[i] ^= data[j];
}
}
else
{
if (data[j] < data[i])
{
data[i] ^= data[j];
data[j] ^= data[i];
data[i] ^= data[j];
}
}
}
}
Console.WriteLine("没有使用委托");
foreach (int item in data)
{
Console.Write("{0,3}", item);
}
Console.WriteLine();
}</span>
调用:

BublleSort(true, 1, 3, 4, 2, 7, 0, 6, 3, 1, 53, 23);
结果:

使用这种硬编码,用一个变量来判断决定,有一个非常大的弊端,就是当你的判断实现代码特别的大时候,容易造成代码臃肿而且结构不清晰。这时候可以使用委托,委托是一个引用类型,这句话有两层意思,首先是个类型,是个类型的话,那就要定义一个委托变量,第二个他是一个引用类型,那他很有可能是某种类,这地方先搁一下。

首先委托类型的申明,这个绕,是类型的申明,委托类型和其他类型不一样(比如int,double等),他需要类型的申明,这再次可以猜测他是某种类。

委托申明如下:

delegate bool SortOption(int a, int b);
看着好别扭,首先是delegate关键字,表明是申明委托类型,其次从结构上看像一个函数,这可以这么理解委托类型是对应的C++的函数指针。然后这个”函数名“就是我们申明的委托类型。这个申明你可以放在类里面也可以放在类外,放在类里面那他的作用域就在这个类里,放在类外那就在这个名字空间内。

现在那就是要定义一个委托变量。可以这么定义:

<pre name="code" class="csharp">            SortOption delegateVariable;
delegateVariable = S2B;



其中的S2B是一个方法名,这个方法的签名要和之前申明的委托类型要一致,即返回类型、形参一致。具体代码如下:

static bool S2B(int a, int b)
{
return a > b;
}


这种委托变量的定义
SortOption delegateVariable;
是不是看起来是不是和委托是某种类的说法矛盾的了。那他还可以这么定义:

SortOption delegateVariable1 = new SortOption(S2B);
这个看起来是不是就像多了,这个要克服,这是因为Microsoft人家热心帮我简化了定义。

比如说我定义方法

        static void BublleSort1(SortOption option, params int[] data)
        {
            for (int i = 0; i < data.Length; i++)
            {
                for (int j = 0; j < i; j++)
                {
                   <span style="color:#ff0000;"> if (option(data[j], data[i]))</span>
                    {
                        data[i] ^= data[j];
                        data[j] ^= data[i];
                        data[i] ^= data[j];
                    }
                }
            }
            Console.WriteLine("使用委托");
            foreach (int item in data)
            {
                Console.Write("{0,3}", item);
            }
            Console.WriteLine();
        }
如果按照上面的定义方法,调用这个方法就可以直接这么写:

BublleSort1(S2B, 1, 3, 4, 2, 7, 0, 6, 3, 1, 53, 23);
但是按照下面的定义方法,要这么写:

BublleSort1(new SortOption(S2B), 1, 3, 4, 2, 7, 0, 6, 3, 1, 53, 23);
从上面的方法的代码实现上看,使用委托确实代码清爽多了,一行解决复杂的逻辑判断问题。

到这,一个简单的委托就这么愉快地实现了,如果你觉得委托就这么一点作用,那就大错特错了。委托最大的作用是多播委托功能,多播(multicast)的意思是多点发送,即将多个方法实现关联在一个委托变量上,通过这个委托变量的调用,来顺序调用这个变量所关联的方法。即发布-订阅模式,首先定义发布者和订阅者,然后发布者和订阅者连接,然后发布者通过发布通知给订阅者,订阅者自己该干嘛干嘛。举个例子,一个房间Room有个空调,空调设置在26度,空调中有两个温控模块一个是加热模块Heater,一个是降温模块Cooler,这两个模块都同时检测房间温度Temperature,如果低于26度,那Heater工作,开始加热,否则Cooler开始工作,开始降温。也就是Room是发布者,Heater和Cooler是订阅者,也就是说房间Room温度的变化TempChanged要动态调用Heater的TempChanged和Cooler的TempChanged。即发布者通过发布通知给订阅者,订阅者执行既定的任务。这个功能特别好,他能分离开发布和执行两个模块,从逻辑上编程思路很清晰。首先分别定义订阅者Heater和Cooler。

class Heater
{
public Heater(double temp)
{
StandardTemp = temp;
}
public double StandardTemp { get; private set; } = 26;
<span style="color:#ff0000;"> public void TempChanged(double temp)
{
if (temp < StandardTemp)
Console.WriteLine("Heatder Start Working");
}</span>
}
class Cooler
{
public Cooler(double temp)
{
StandardTemp = temp;
}
public double StandardTemp { get; private set; } = 26;
<span style="color:#ff0000;">public void TempChanged(double temp)
{
if (temp > StandardTemp)
Console.WriteLine("Cooler Start Working");
}</span>
}
定义发布者Room类,通过TempChanged想订阅者发布通知。

class Room
{
public TempChangedHandler TempChanged;
double StandardTemp = 26;
private double _Temperature;

public double Temperature
{
get { return _Temperature; }
set
{
if (value != StandardTemp)
<span style="color:#ff0000;">    TempChanged(value);</span>
_Temperature = value;
}
}
}
Main方法里将发布者和订阅进行连接。

Room room = new Room();
Cooler cooler = new Cooler(26);
Heater heater = new Heater(26);
<span style="color:#ff0000;">            room.TempChanged += cooler.TempChanged;
room.TempChanged += heater.TempChanged;</span>
Console.WriteLine("当温度是25度时");
room.Temperature = 25;
Console.WriteLine("当温度是24度时");
room.Temperature = 24;
Console.ReadKey();
运行结果:

代码写到这,好像特别完美,但是这里面有两个重大弊端,第一个是订阅者和发布者的连接是通过+=操作符来实现的,这个不安全,因为首先发布者在发布通知的时候需要检查委托变量是否为null,因为如果为null,程序出错,所以上面代码

<span style="color:#ff0000;">  TempChanged(value);</span>
需要改成:

TempChangedHandler localChanged = TempChanged;
if (localChanged != null)
TempChanged(value);
这个地方非常有意思,他并没有直接判断TempChanged是否为空,而是先赋值给一个局部变量localChanged,然后通过这个局部变量来判断是否为空。这有什么作用呢?多播委托经常会用在多线程程序中,如果在直接判断TempChanged是否为空之后,TempChanged()调用之前,其他线程调用了room.TempChanged-=XX操作,使room.TempChanged为null,这样程序就会出错,而且会莫名其妙。哈哈哈····,但是如果先赋值给一个局部变量,为什么就能避免这种情况呢?按理来说,委托是一个引用类型,赋值给一个局部变量,局部变量localChanged和TempChanged指向的是同一个位置,TempChanged的变化应该在localChanged表现出来,但是情况并不是这样,之前我们提到过委托变量定义可以使用 TempChangedHandler
localChanged=XX方法名 它等价于TempHandler localChanged=new TemChangedHandler(XX方法名),这表明当一个委托类型调用=、+=、-=操作符(-=、+=最终还是=操作符嘛)的时候,委托类型其实是重新生成了一个新的实例对象。这样就能解释以上通过局部变量判断非空的原因了。但是无论怎么样,这个非空判断是可以可以优化的,Microsoft应该给我们做一个优化,所以我们一会要讲事件Event事件对这个情况就进行了优化,委托还有一个非常不好的弊端就是他没法对委托变量隐藏,即不能使用private类型修饰,因为如果用private修饰就不能在类的外部为委托添加方法了。这导致可以在类外部调用委托变量,这当然是不符合逻辑的,因为TempChanged是应该根据温度变化点用的,而这里温度很明显没有发生变化。
room.TempChanged(0);



                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: