C# 将事件视为对象
C#还不够吗?这里我举了一个(可能是坏的)例子C# 将事件视为对象,c#,.net,asp.net,events,oop,C#,.net,Asp.net,Events,Oop,C#还不够吗?这里我举了一个(可能是坏的)例子 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 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并没有“真正”传递给一个方法。对RegisterLogger进行以下更改,声明
ev
作为事件处理程序的引用参数
static void RegisterLogger(ref EventHandler ev)
{
ev += LogOnStart;
}
然后,您的调用点在调用方法时还需要使用'ref'关键字,如下所示
RegisterLogger(ref p.OnStart);
对RegisterLogger进行以下更改,将
ev
声明为事件处理程序的引用参数
static void RegisterLogger(ref EventHandler ev)
{
ev += LogOnStart;
}
然后,您的调用点在调用方法时还需要使用'ref'关键字,如下所示
RegisterLogger(ref p.OnStart);
无法编译的原因: 寄存器记录器(cs.OnStart) 。。。事件处理程序和将其传递给的方法位于不同的类中。C#非常严格地处理事件,只允许事件出现在其中的类执行除添加处理程序以外的任何操作(包括将其传递给函数或调用它) 例如,它也不会编译(因为它位于不同的类中): 至于不能以这种方式将事件处理程序传递给函数,我不确定。我猜事件就像值类型一样运行。通过ref传递它将解决您的问题,不过:
static void RegisterLogger(ref EventHandler ev)
{
ev += LogOnStart;
}
无法编译的原因: 寄存器记录器(cs.OnStart) 。。。事件处理程序和将其传递给的方法位于不同的类中。C#非常严格地处理事件,只允许事件出现在其中的类执行除添加处理程序以外的任何操作(包括将其传递给函数或调用它) 例如,它也不会编译(因为它位于不同的类中): 至于不能以这种方式将事件处理程序传递给函数,我不确定。我猜事件就像值类型一样运行。通过ref传递它将解决您的问题,不过:
static void RegisterLogger(ref EventHandler ev)
{
ev += LogOnStart;
}
当一个对象声明一个事件时,它只向类外的事件公开添加和/或删除处理程序的方法(前提是它不重新定义添加/删除操作)。在它内部,它被视为一个“对象”,工作起来或多或少像一个声明的委托变量。如果没有向事件添加任何处理程序,就好像它从未初始化过一样,并且
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;
}
}
当一个对象声明一个事件时,它只向类外的事件公开添加和/或删除处理程序的方法(前提是它不重新定义添加/删除操作)。在它内部,它被视为一个“对象”,工作起来或多或少像一个声明的委托变量。如果没有向事件添加任何处理程序,就好像它从未初始化过一样,并且
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;
}
}
“事件XXX只能出现在+=或-=blabla的左侧”
这实际上是因为C#是“OO够了。”OOP的核心原则之一是封装;事件提供了这样一种形式,就像属性一样:在声明类内部,可以直接访问它们,但在外部,它们只向+=
和-=
操作符公开。这使得声明类完全控制调用事件的时间。客户机代码只能对调用它们时发生的事情有发言权
代码RegisterLogger(p.OnStart)
编译的原因是它是在Program
类的范围内声明的,在该类中声明了Program.OnStart
事件
代码RegisterLogger(cs.OnStart)
没有编译的原因是它是在程序类的范围内声明的,但是MyCSharpProgram.OnStart
事件(显然)是在MyCSharpProgram
类内声明的
正如所指出的,在p.OnStart(p,EventArgs.Empty)行上获得NullReferenceException
的原因
是指调用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
作为参数并为其分配新值的方法也只会影响其局部变量(在分配中)
“事件XXX只能出现在+=或-=blabla的左侧”
这实际上是因为C#是“OO够了。”OOP的核心原则之一是封装;事件提供了这样一种形式,就像属性一样:在声明类内部,可以直接访问它们,但在外部,它们只向+=
和-=
操作符公开。这使得声明类完全控制调用事件的时间。客户机代码只能对调用它们时发生的事情有发言权
代码RegisterLogger(p.OnStart)
编译的原因是它是在