C#还不够OO吗?在这里,我给出一个(可能是坏的)例子。
public class Program
{
public event EventHandler OnStart;
public static EventHandler LogOnStart = (s, e) => Console.WriteLine("starts");
public class MyCSharpProgram
{
public string Name { get; set; }
public event EventHandler OnStart;
public void Start()
{
OnStart(this, EventArgs.Empty);
}
}
static void Main(string[] args)
{
MyCSharpProgram cs = new MyCSharpProgram { Name = "C# test" };
cs.OnStart += LogOnStart; //can compile
//RegisterLogger(cs.OnStart); // Line of trouble
cs.Start(); // it prints "start" (of course it will :D)
Program p = new Program();
RegisterLogger(p.OnStart); //can compile
p.OnStart(p, EventArgs.Empty); //can compile, but NullReference at runtime
Console.Read();
}
static void RegisterLogger(EventHandler ev)
{
ev += LogOnStart;
}
}
RegisterLogger(cs.OnStart)导致编译错误,因为“事件XXX只能出现在+ =或 - = blabla的左侧”。但为什么RegisterLogger(p.OnStart)可以?同时,虽然我注册了p.OnStart,但它也会抛出一个NullReferenceException,似乎p.OnStart并没有“真正”传递给一个方法。
答案 0 :(得分:1)
对RegisterLogger进行以下更改,将ev
声明为事件处理程序的参考参数。
static void RegisterLogger(ref EventHandler ev)
{
ev += LogOnStart;
}
然后,您的呼叫点在调用方法时也需要使用'ref'关键字,如下所示
RegisterLogger(ref p.OnStart);
答案 1 :(得分:1)
无法编译的原因:
RegisterLogger(cs.OnStart);
...是事件处理程序和传递给它的方法在不同的类中。 C#非常严格地处理事件,并且只允许事件出现的类除了添加处理程序之外做任何事情(包括将其传递给函数或调用它)。
例如,这也不会编译(因为它在不同的类中):
cs.OnStart(cs, EventArgs.Empty);
至于无法以这种方式将事件处理程序传递给函数,我不确定。我猜测事件就像值类型一样运作。通过ref传递它将解决您的问题,但是:
static void RegisterLogger(ref EventHandler ev)
{
ev += LogOnStart;
}
答案 2 :(得分:1)
“事件XXX只能出现在+ =或 - = blabla的左侧”
这实际上是因为C# “足够OO”。 OOP的核心原则之一是封装;事件提供了一种形式,就像属性一样:在声明类中,它们可以直接访问,但在外部它们只暴露给+=
和-=
运算符。这样,声明类就可以完全控制事件的调用时间。客户端代码只能在调用时发表意见。
您的代码RegisterLogger(p.OnStart)
编译的原因是它是在Program
类的范围内声明的,其中声明了Program.OnStart
事件。
您的代码RegisterLogger(cs.OnStart)
执行非编译的原因是它是在Program
类的范围内声明的,但声明了MyCSharpProgram.OnStart
事件(显然)在MyCSharpProgram
级内。
正如Chris Taylor指出的那样,您在NullReferenceException
行上获得p.OnStart(p, EventArgs.Empty);
的原因是,您正在调用RegisterLogger
为其分配新值一个局部变量,当它作为参数传入时,对该局部变量所分配的对象没有影响。要更好地理解这一点,请考虑以下代码:
static void IncrementValue(int value)
{
value += 1;
}
int i = 0;
IncrementValue(i);
// Prints '0', because IncrementValue had no effect on i --
// a new value was assigned to the COPY of i that was passed in
Console.WriteLine(i);
正如将int
作为参数并为其分配新值的方法仅影响复制到其堆栈的局部变量一样,该方法将EventHandler
作为参数并分配 it 的新值仅影响其局部变量(在作业中)。
答案 3 :(得分:0)
当一个对象声明一个事件时,它只公开向该类外部的事件添加和/或删除处理程序的方法(前提是它不重新定义添加/删除操作)。在其中,它被视为“对象”,并且或多或少地像声明的委托变量一样工作。如果没有向事件添加处理程序,就好像它从未被初始化并且是null
。这是设计方式。以下是框架中使用的典型模式:
public class MyCSharpProgram
{
// ...
// define the event
public event EventHandler SomeEvent;
// add a mechanism to "raise" the event
protected virtual void OnSomeEvent()
{
// SomeEvent is a "variable" to a EventHandler
if (SomeEvent != null)
SomeEvent(this, EventArgs.Empty);
}
}
// etc...
现在,如果您必须坚持将代理暴露在课堂之外,请不要将其定义为事件。然后,您可以将其视为任何其他字段或属性。
我修改了您的示例代码以说明:
public class Program
{
public EventHandler OnStart;
public static EventHandler LogOnStart = (s, e) => Console.WriteLine("starts");
public class MyCSharpProgram
{
public string Name { get; set; }
// just a field to an EventHandler
public EventHandler OnStart = (s, e) => { /* do nothing */ }; // needs to be initialized to use "+=", "-=" or suppress null-checks
public void Start()
{
// always check if non-null
if (OnStart != null)
OnStart(this, EventArgs.Empty);
}
}
static void Main(string[] args)
{
MyCSharpProgram cs = new MyCSharpProgram { Name = "C# test" };
cs.OnStart += LogOnStart; //can compile
RegisterLogger(cs.OnStart); // should work now
cs.Start(); // it prints "start" (of course it will :D)
Program p = new Program();
RegisterLogger(p.OnStart); //can compile
p.OnStart(p, EventArgs.Empty); //can compile, but NullReference at runtime
Console.Read();
}
static void RegisterLogger(EventHandler ev)
{
// Program.OnStart not initialized so ev is null
if (ev != null) //null-check just in case
ev += LogOnStart;
}
}