昨天有个朋友问我在Unity3D中如何使用代理。我简单的给他说了一下,可是他还是希望我能写出来。既然不是很麻烦,那么我就在博客里把这个例子写出来。C#语言是支持代理的,并且代理是非常的好用的一种方式。简单的来说就是 你委托朋友帮你做一件事情,当你的朋友做完以后会告诉你。 代码中一般A、B、C、D若干类同时委托E类来做一件事情,当这件事情E类完成时会同时回调A、B、C、D类中的方法,大概就是这个意思拉。
回到Unity3D中,我们写一个简单的例子,Test.cs委托Log.cs 办一件事情,当Log.cs办完后在Test.cs回调委托的方法。代码比较简单我们直接将Test.cs Log.cs 绑在摄像机上,运行就能看到效果。。上代码!!!
Log.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
using UnityEngine; using System.Collections; public class Log :MonoBehaviour { public delegate void DoOnething(string str); public static event DoOnething doOne; public delegate void DoTwothing(string str); public static event DoTwothing doTwo; void Start () { if(doOne != null) { doOne("yusong"); } if(doTwo != null) { doTwo("momo"); } } } |
我解释一下。委托的标识符就是delegate ,上述代码的DoOnething(string str) DoTwothing(string str)就是委托完毕后回调的方法名称,doOne doTwo这两个是委托的事件,在Start方法中通过委托的事件将委托的参数传递进去即可实现委托。在开发中应该有一个专门用来委托的类,在DoOne DoTwo方法之上应该执行对应对托的相关代码。不一定是在Start方法中,静态调用对象皆可。
接着再看看Test.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using UnityEngine; using System.Collections; public class Test : MonoBehaviour { void OnEnable() { Log.doOne += DoOnething; Log.doTwo += DoTwothing; } void OnDisable() { Log.doOne -= DoOnething; Log.doTwo -= DoTwothing; } public void DoOnething(string str) { Debug.Log("DoOnething " + str); } public void DoTwothing(string str) { Debug.Log("DoTwothing " + str); } } |
在OnEnabel和OnDisable中启动与关闭委托,这也是为什么Log.cs中要判断一下委托是否为空。如果为空的话是会有错误的。比如
Log.doOne += DoOnething;
这段代码的意思就是为委托事件赋值,让本类的DoOnethine(string str)方法接受委托传递回来的事件,这里起到一个回调的作用。
Log.doOne -= DoOnething;
就是释放当前的委托事件,因为Log.doOne是一个静态类型的变量,不用的时候把他关掉,一旦关掉后,DoOnething方法就接收不到回调信息了,除非再次 += 添加。
最后就是当Log.cs中调用 doOne(“yusong”);的时候,实际上就是回调Test.cs中的 DoOnething(string str);
运行一下看看效果大家就明白啦。
本文比较简单,也没什么代码。纯属帮忙写点东西,欢迎大家讨论!!
关于详细代理和事件的区别,下面有篇文章,写的很不错,转给大家。
C#的代理和事件
原文: http://www.yaosansi.com/post/1119.html
重新温习下,刚好看见,转过来.
==============================================
作者: Eric Gunnerson
代理 (Delegate)
大多数情况下,当调用函数时我们会指定要直接调用的函数。比如类 MyClass 如具有一个名为 Process 的函数,我们通常会按如下方法进行调用:
MyClass myClass = new MyClass();
myClass.Process();
这种调用在大多数情况下都是可行的。但是有些时候,我们不想直接调用函数,而希望能够将它传递给其他人,让他们进行调用。在以事件驱动的系统(如图形用户 界面)中,这种方法尤为有用。例如当我需要在用户单击某个按钮即可执行一些代码时,或者当我要记录一些信息但却无法指定记录方式时。
考虑以下示例:
public class MyClass
{
public void Process()
{
Console.WriteLine(“Process() begin”);
// 这里还有其他东西…
Console.WriteLine(“Process() end”);
}
}
在此类中,我们进行一些记录,以了解函数的开始时间和结束时间。但是,我们的记录仅限于发送到控制台,这可能不是我们所需要的。我们真正需要的是能够控制从函数外部记录信息的位置,同时不必使函数代码变得复杂。
在这种情况下,代理便是理想的解决方法。代理使我们可以指定将要调用的函数,看起来好像不需要指定哪个函数一样。对代理的声明类似于对函数的声明,不同的是在这种情况下,我们所声明的是此代理可引用的函数签名。
我们的例子将声明一个带有单个字符串参数且没有返回类型的代理。修改该类如下:
public class MyClass
{
public delegate void LogHandler(string message);
public void Process(LogHandler logHandler)
{
if (logHandler != null)
logHandler(“Process() begin”);
// 这里还有其他东西
if (logHandler != null)
logHandler (“Process() end”);
}
}
使用代理与直接调用函数相似。只是在调用函数前,我们需要检查代理是否为空(即不指向一个函数)。
要调用 Process() 函数,我们需要声明一个与代理相匹配的记录函数,然后创建代理的实例,以指向该函数。然后,将此代理传递给 Process() 函数。
class Test
{
static void Logger(string s)
{
Console.WriteLine(s);
}
public static void Main()
{
MyClass myClass = new MyClass();
MyClass.LogHandler lh = new MyClass.LogHandler(Logger);
myClass.Process(lh);
}
}
Logger() 函数是一个我们要从 Process() 函数中调用的函数,我们对它进行了声明,使其与代理相匹配。在 Main() 中,我们创建代理的一个实例,然后将该函数传递给代理构造函数,使其指向该函数。最后,我们将代理传递给 Process() 函数,该函数接着调用 Logger() 函数。
如果您习惯于使用 C++ 语言,您可能会认为代理很像函数指针,这种想法非常接近于事实。但是,代理并不“仅仅”是函数指针,它还提供了其它多种功能。
传递状态 (Passing State)
在上面的简单示例中,Logger() 函数仅仅是输出些字符串。一个不同的函数可能把信息记录到文件中,但是要进行这种操作,该函数需要知道把信息写道什么文件中。
对于 Win32® 而言,当您传递函数指针时,可随之传递状态。但是对于 C#,这就没有必要了,因为代理既可指向静态函数,“也”可指向成员函数。以下是一个有关如何指向成员函数的示例:
class FileLogger
{
FileStream fileStream;
StreamWriter streamWriter;
public FileLogger(string filename)
{
fileStream = new FileStream(filename, FileMode.Create);
streamWriter = new StreamWriter(fileStream);
}
public void Logger(string s)
{
streamWriter.WriteLine(s);
}
public void Close()
{
streamWriter.Close();
fileStream.Close();
}
}
class Test
{
public static void Main()
{
FileLogger fl = new FileLogger(“process.log”);
MyClass myClass = new MyClass();
MyClass.LogHandler lh = new MyClass.LogHandler(fl.Logger);
myClass.Process(lh);
fl.Close();
}
}
FileLogger 类仅封装文件。我们修改了Main()以使代理指向 FileLogger 的 fl 实例的 Logger() 函数。当从 Process() 中激活此代理时,将会调用成员函数并把字符串记录到相应的文件中。
其优点在于,我们不必更改 Process() 函数 -对代理来说代码都是相同的,无论引用的是静态函数还是成员函数,。
多播 (Multicasting)
虽然指向成员函数的功能已让人感到满意,但利用代理,您还可以巧妙地完成其它一些任务。在 C# 中,代理是“多播”的,这表示它们可同时指向一个以上的函数(即基于 System.MulticastDelegate 类型)。多播代理将维护一个函数列表。当调用该代理时,将会调用列表中的所有函数。我们可以添加第一个示例中的记录函数,然后调用这两个代理。要将两个代理组合起来,可使用 Delegate.Combine() 函数。其代码如下:
MyClass.LogHandler lh = (MyClass.LogHandler)
Delegate.Combine(new Delegate[]
{new MyClass.LogHandler(Logger),
new MyClass.LogHandler(fl.Logger)});
啊呀,真的是很难看!幸好 C# 提供了一种更好的语法,而不用将以上语法强加给用户。无需调用 Delegate.Combine(),仅使用 += 即可组合这两个代理:
MyClass.LogHandler lh = null;
lh += new MyClass.LogHandler(Logger);
lh += new MyClass.LogHandler(fl.Logger);
这样就简洁多了。要从多播代理中删除一个代理,可调用 Delegate.Remove() 或使用 -= 运算符(我知道自己会用哪一个)。
当你调用多播代理时,就会按出现顺序对调用列表中的代理进行同步调用。如果此过程中出现了错误,执行过程即被中断。
如果您想更严格地控制调用顺序(例如要进行万无一失的调用),则可以从代理中获取调用列表,然后自行调用这些函数。以下是一个示例:
foreach (LogHandler logHandler in lh.GetInvocationList())
{
try
{
logHandler(message);
}
catch (Exception e)
{
// 在这里处理异常情况吗?
}
}
代码只是将每次调用包装在一个 try-catch 对中,这样在一个调用处理(handler)中引发的异常就不会妨碍对其它调用处理(handler)的激活。
事件 (Events)
我们已经对代理进行了较长时间的讨论,现在该谈一谈事件了。一个显而易见的问题就是:“既然我们已经有了代理,为什么还需要事件?”
回答这个问题的最好方法就是考虑用户界面对象所发生的事件。例如,一个按钮可能有公共的“Click”代理。我们可将一个函数挂接到该代理上,这样当单击此按钮时,就可以调用该代理。例如:
Button.Click = new Button.ClickHandler(ClickFunction);
它表示当单击此按钮时,将调用 ClickFunction()。
小测验:上述代码是否存在问题?我们忘记了什么?
答案是,我们忘记使用 += 而直接分配了代理。这表示其它任何挂接到“Button.Click”的代理现在都将解除挂接。“Button.Click”应该是公共的,以便其它对象可以对其进行访问,因此上述情况将无法避免。同样,要删除代理,用户可能会编写以下代码:
Button.Click = null;
这将删除所有代理。
这些情形极其糟糕,因为在许多情况下只挂接了一个代理,问题不会明显地表现为bug。随后,当挂接了另一个代理时,事情就糟了!
事件在代理模型上添加了一层保护。这里有一个支持事件的对象例子:
public class MyObject
{
public delegate void ClickHandler(object sender, EventArgs e);
public event ClickHandler Click;
protected void OnClick()
{
if (Click != null)
Click(this, null);
}
}
ClickHandler 代理使用事件代理的标准模式来定义事件的签名。它以handler的名字结尾,带有两个参数。第一个参数是发送此事件的对象,第二个参数用于传递伴随事件发生的信息。本例中没有要传递的信息,因此直接使用 EventArgs;但是如果有数据要传递,则使用从 EventArgs 派生的类(例如 MouseEventArgs)。
“Click”事件的声明做两件事情:首先,它声明一个名为“Click”的代理成员变量,在类的内部使用。其次,它声明一个名为“Click”的事件,该事件可按照常规访问规则从类的外部进行使用(在此例中,事件为公共事件)。
一个像OnClick()这样的函数通常包含进去以便该类型或从该类型的派生类型可以触发事件。由于“Click”是代理,您将会注意到,用来触发事件的代码与代理的代码相同。
与代理类似,我们使用 += 和 -= 来挂接或解除事件挂接,但与代理不同的是,仅可对事件执行这些操作。这可确保不会发生先前所讨论的两种错误。
使用事件是很简单的事情。
class Test
{
static void ClickFunction(object sender, EventArgs args)
{
// process the event here.
}
public static void Main()
{
MyObject myObject = new MyObject();
myObject.Click += new MyObject.ClickHandler(ClickFunction);
}
}
我们创建一个与代理签名相匹配的静态函数或成员函数,然后用 += 向事件中添加代理的一个新实例。
- 本文固定链接: https://www.xuanyusong.com/archives/1895
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
http://www.tracefact.net/csharp-programming/delegates-and-events-in-csharp.aspx看这篇文章吧,解释得比较清楚。
看了momo的几十篇博客,大多停留在知其然的层面,很少有只言片语道破本质的文章。作为入门级的体验tutorial兴许不错。 anyway, 谢谢你耐心地和大家分享,也期待你的成长和更精彩的分享。
呵呵, 可能我还没到拿水平吧。 而且我这些文章都是打发时间写的。 人嘛总得有点爱好。。 有些也写的有问题。
MOMO,你转的文章写的不对哦。文章中写只有event可以使用 =和-=,我实际测试后,发现加不加event都可以使用 =和-=,而且效果一样。我目前还没有找出加event和不加event的使用区别。
MOMO,你转的文章写的不对哦。文章中写只有event可以使用+=和-=,我实际测试后,发现加不加event都可以使用+=和-=,而且效果一样。我目前还没有找出加event和不加event的使用区别。
第一次看这篇博客的时候感觉特别模糊,从网上找了一个说的比较详细的。如果这篇博文没读懂,看看这个吧,链接在此http://www.cnblogs.com/hyddd/archive/2009/07/26/1531538.html
关于C#内置事件机制跟unity3d自带的sendmessage两者,该如何取舍?
差不多。要用的话就用C#自带的吧。。
彪悍的松松!!!
强悍的 小马!!
MOMO老师,请教一个问题哈,我如果想用蓝牙手柄控制ios设备上的游戏按键,我在unity程序中应该怎么做?能不能给点思路?
实现底层的蓝牙协议。。就可以做到的。。 在OC中获取蓝牙信息 把蓝牙的消息传递给unity就可以
彪悍的人生不需要解释。。