使用初始化器语法初始化事件

7

我经常想写这样的东西:

new Form
{
    Text = "Caption",
    Controls =
    {
        new Button { Text = "Button 1", Click = (s, e) => MessageBox.Show("Button 1 Clicked"), Location = new Point(10, 10) },
        new Button { Text = "Button 2", Click = new EventHandler(Button2Clicked), Location = new Point(10, 40) },
        new Button { Text = "Button 3", Click = Button3Clicked, Location = new Point(10, 70) },
    },
}

初始化语法只是一种简化方式,那么为什么编译器不能推断出如何生成事件订阅的代码呢?

给我一些糖,宝贝!

当初始化语法被发明时,肯定有人考虑过事件并且拒绝了它们。我一直在试图想象可能的理由,但是一无所获。

是因为事件是一个可以有多个订阅者的多播对象吗?不,这是一个初始化过程;不可能有其他订阅者。[已更新] 不对,初始化器是在构造后应用的,一个对象可以订阅自己的事件

致Eric的一封信:我听过“为什么C#没有实现X功能”的演讲。但在这种情况下,已经有人在实现初始化器了。

更新

似乎有争议/混淆,因为我在示例中使用了Click =。实际的语法与问题无关。它可以很容易地是Click +=,这反映了通常必须添加处理程序的方式。我更喜欢前者,因为它与其余初始化程序语法一致,但最终我不在意,只要我能在初始化程序列表中订阅事件。

另一个更新

我确实意识到现在添加该功能可能不太可能。首先想到的问题是Intellisense必须得到更新。可能还有许多其他事情会妨碍现在添加此功能。我的问题是:为什么他们一开始没有添加它。肯定存在某些引人注目的原因导致否决投票。


事件不能使用赋值运算符,你必须使用 +=,所以这个语法是不正确的(因为 Click 是一个事件),对吗? - Chris Pfohl
我认为这与“当前合法的语法”无关。这是一种额外的语法,特定于初始化程序。 - Tergiver
可能是在对象初始化器中分配事件的重复问题。 - Ben Voigt
4个回答

6

我不明白为什么他们不能提供这一小勺糖,我想他们可能只是没有想到!

事件中已经涉及了相当多的语法糖,如果只是在类上声明一个事件而没有提供自己的实现,编译器会为你提供一个委托后备字段,并添加/删除“方法”实现。此外,当您添加事件处理程序时,编译器使用委托推断,允许您简单地指向一个方法,而不是创建代表该方法的委托。

有趣的是,Mono C#确实允许您在对象初始化程序中添加事件处理程序:

http://tirania.org/blog/archive/2009/Jul-27-1.html

是时候转向Mono了;-)


好吧,至少Mono的人支持我。 - Tergiver
这一段代码有问题。你不应该在对象初始化器内部或外部将某些东西分配给事件成员。它是一个事件,而不是可设置的属性。 - Asad Saeeduddin

2
“fields”和“events”之间有很大的区别。这里有一篇很好的文章here概述了它们之间的区别,但这就是你问题的答案:一个字段可以被赋值;一个事件看起来像一个字段,但实际上是完全不同的东西。
从我链接的文章中可以看到:
我们已经看到,事件关键字是委托声明的修饰符,允许它被包含在接口中,在声明它的类内限制其调用,提供它一对可定制的访问器(添加和删除),并强制委托的签名
请记住,“event”是一个快捷方式;在幕后,编译器创建了一个具有“add()”和“remove()”方法的对象。如下:
public class Button {

    public event EventHandler Click {
        void add {...}
        void remove {...}
    }

}

也许这会提供一些见解...
Button btn = new Button {Click += (s, e) => MessageBox.Show("hello")};

你收到的错误信息是“无法使用集合初始化程序初始化类型为'Button'的对象,因为它没有实现IEnumerable接口”。
还有一个注意事项...如果你在表单内分配事件处理程序,可以这样做:
this.button1.Click += (s, e) => this.textBox1.Text = e.ToString();

您无法从您创建的代码中访问表单变量。我理解您的想法,我并不反对...您所做的事情可能能够实现。我想我的观点是,决定不让它工作的原因是有原因的。

我认为,从初始化的角度来看,字段和事件之间没有区别。 - Tergiver
我很感谢您的输入。对于“您无法从所创建的代码中访问表单变量”这一点,我有异议。在我提供的代码中并没有“表单变量”。它们不是必要的,实际上从来都不必要。表单设计器之所以会为您提供它们(除非您告诉它不要这样做),只是为了方便起见。 - Tergiver
1
初始化器语法用于初始化字段或属性。属性与字段具有与事件相同的关系。它们都是一对由编译器合成的方法或程序员提供的方法的简写。因此,我认为字段与事件之间的比较无效。您的错误消息也不够明确,因为花括号初始化根据上下文有两个含义。编译器决定您正在尝试进行集合初始化,但这也是无效的。 - Justin Aquadro

2
尝试简单地分配一个事件:
Click = (o,e) => { <CODE> }

无效。初始化程序仅适用于像这样可以直接分配的东西。这是因为事件需要能够通知他们想要的任何人(您不应该意外删除其他人对该事件的注册)。

我不确定这是否是他们的理由,但对我有效。


我再次提出反驳意见,即在初始化期间事件保证为空,但如果人们更喜欢,可以说语法应该是 Click += (s, e) => { <CODE> } - Tergiver
不一定。如果您的初始化程序使用任何方法来设置值,那么该方法可能会具有订阅事件的副作用。 - Chris Pfohl
啊,你提到了我最喜欢的一个小烦恼。首先,你提出了一个必须考虑的有效观点(在我的看法中可以通过 += 语法解决)。其次,这就是我对自我订阅的看法:http://tergiver.wordpress.com/2011/01/20/self-subscription-is-asinine/。 - Tergiver

1

是的,应该是语言的一部分!

但是,这里有一个棘手的解决方法,让您可以在初始化器列表中订阅事件...

public class TestClass
{
    public class MyButton : Button
    {
        public EventHandler ClickSubscriber
        {
            get { return null; }
            set { Click += value; }
        }
    }

    public static void RunTest()
    {
        new Form
            {
                Text = "Caption",

                Controls =
                    {
                        new MyButton 
                            { 
                                ClickSubscriber = (s, e) => 
                                     MessageBox.Show("Button 1 Clicked"), 
                            },
                    },
            };
    }        
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接