Initializing events with initializer syntax - c#

I often want to write something like this:
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) },
},
}
Initializer syntax is just sugar, so why can't the compiler figure out how to generate code for an event subscription?
Gimme some sugar, baby!
When initializer syntax was invented, someone must have thought about events and rejected them. I've been trying to imagine what the rationale might have been and am coming up blank.
Is it because an event is a multi-cast object that might have more than one subscriber? No, this is an initialization process; There can be no other subscribers. [Updated] Not true, initializers are applied post-construction and an object can subscribe to its own events.
A note to Eric: I've heard the Why doesn't C# implement feature X speech. In this case, someone was already there, implementing initializers.
Updated
There seems to be contention/confusion because I used Click = in my example. The actual syntax is not relevant to the question. It could just as easily be Click += which mirrors the way you have to add a handler normally. I prefer the former because it's consistant with the rest of the initializer syntax, but ultimately I don't care, just so long as I can subscribe to an event in an initializer list.
Another Update
I do realize that adding the feature now is probably unlikely. The first issue that comes to mind is that Intellisense has to be updated. There are probably many other things that would hinder adding this feature now. My question is: Why didn't they add it in the first place. There must have been something compelling that warrented the 'nay' vote.

I cannot see any reason why they could not have provided this small teaspoon of sugar, I guess they just didn't!
There is already quite a lot of syntactic sugar involved in events, if simply declare an event on a class without providing your own implementation, the compiler is providing a delegate backing field for you, plus add / remove 'method' implementations. ALso, when you add an event handler, the compiler uses delegate inference, allowing you to simply point to a method, rather than create a delegate that represents the method.
Interestingly, Mono C# does allow you to add an event handler in an object initializer:
http://tirania.org/blog/archive/2009/Jul-27-1.html
Time to switch to Mono ;-)

Try simply assigning an event:
Click = (o,e) => { <CODE> }
Doesn't work. Initializers work only with things you can directly assign like that. This is because events need to be able to notify anyone they want (you shouldn't be allowed to remove someone else's registration for that event on accident).
I'm not sure if this is their reasoning, but it works for me.

There's a big difference between fields and events. There's an excellent article here outlining the differences, but that's the answer to your question: A field can be assigned a value; an event looks like a field but is a very different beast.
Edit
From the article I linked to:
We have seen that the event keyword is a modifier for a delegate declaration that allows it to be included in an interface, constrains its invocation from within the class that declares it, provides it with a pair of customizable accessors (add and remove), and forces the signature of the delegate
Remember that event is a shortcut; behind the scenes, the compiler creates an object with add() and remove() methods. Like:
public class Button {
public event EventHandler Click {
void add {...}
void remove {...}
}
}
Perhaps this will offer some insight... :
Button btn = new Button {Click += (s, e) => MessageBox.Show("hello")};
The error message you get is "Cannot initialize type 'Button' with a collection initializer because it does not implement IEnumerable"
Still another note... if you assign the event handler from within the form, you can do this:
this.button1.Click += (s, e) => this.textBox1.Text = e.ToString();
You couldn't access form variables from the code you've created. I get where you're coming from, and I don't disagree... what you're doing could be made to work. I guess my point is that there are reasons why the decision was made not to make it work.

Yep, should be part of the language!
But, here's a tricky workaround that lets you subscribe to events within an initializer list...
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"),
},
},
};
}
}

Related

Declare and Initialize a ToolStripMenuItem with an Event Handler

I have a ToolStripMenuItem that I want to declare and instantiate with a String, a null value for an image, and an Event Handler for its Click event. This is the format Intellisense is expecting:
ToolStripMenuItem(string text, System.Drawing.Image image, EventHandler onClick).
But I am unable to assign the Event Handler and I do not know the proper syntax to do so. As a workaround, I assign the .Click event in the constructor like so...
class Timer
{
//The other WinForms objects and my methods are omitted.
private ToolStripMenuItem StartButton = new ToolStripMenuItem("Start Timer");
public Timer()
{
//I want the assignment of StartButton_Click in my declaration and initialization of StartButton, not here.
StartButton.Click += new EventHandler(StartButton_Click);
}
public void StartButton_Click(object sender, EventArgs e)
{
//The logic here is not relevant.
}
}
I tried the syntax below but I keep getting the error:
"CS0236 A field initializer cannot reference the non-static field, method, or property 'Timer.StartButton_Click(object, EventArgs)'"
new ToolStripMenuItem("Start Timer", null, new EventHandler(StartButton_Click));
Intelliense suggests I use the format
EventHandler(void(object,EventArgs)target)
but I do not know how to fill out the expected syntax property. How do I write the declaration of StartButton so that the method StartButton_Click is called after a Click event?
The correct place to instantiate it is in the constructor. Do it all at once, like this:
private ToolStripMenuItem StartButton;
public Timer()
{
StartButton = new ToolStripMenuItem("Start Timer", null, StartButton_Click);
}
As for that compiler error, you can read more about it here, although it's sparse on the details.
From Stack Overflow: You cannot use an instance variable to initialize another instance variable. Why? Because the compiler can rearrange these - there is no guarantee that reminder will be initialized before StartButton_Click, so the above line might throw a NullReferenceException.
Make the method static and you should be good to go.

How to properly send different parameters down for a standard Button Click

I know this has to have an easy answer, but I'm utterly failing to fathom the wealth of information on custom events, event handlers, and delegates. I have a custom messagebox class. I am trying to add the capability to do something based off of the state of a check box if the OK button is clicked. The buttons and the checkbox are added dynamically based upon input into a static Show method somewhat like the following:
if (!String.IsNullOrWhiteSpace(suicideCheckboxID))
{
suicideCheckBox = new CheckBox();
suicideCheckBox.AutoSize = true;
suicideCheckBox.Text = "Do not show this message again.";
suicideCheckBox.Location = new Point(xMargin, label.Bottom + yMargin);
suicideCheckBox.Checked = false;
suicideCheckBoxHeight = suicideCheckBox.Height;
form.Controls.Add(suicideCheckBox);
}
Button okButton = NewButton(DialogResult.OK, scaleFactor);
int x = (form.Width - okButton.Width) / 2;
okButton.Location = new Point(x, buttonYPosition);
form.Controls.Add(okButton);
form.AcceptButton = okButton;
form.CancelButton = okButton;
That's not the exact code, but it's fairly representative. My impulse is to use okButton.Clicked += new EventHandler(OKButton_clicked), but if I do that, the event generated only carries arguments for object sender and EventArgs e and I really need it to operate off of the state of the checkbox and an additional piece of text to indicate which messagebox is being shown so that the values can be stored in the registry.
My first attempt was to do something like okButton.Clicked += processSuicideCheckbox(suicideCheckboxID, suicideCheckBox);, but that seems to just process the contents and allow one to return an EventHandler that points to a method with the signature of object sender and EventArgs e. What am I missing here? What is the best way to pass in the arguments actually relevant to me?
You don't get to choose what is in the event handler for the Click event. Microsoft has already done that. You are stuck with the (object sender, EventArgs e) signature.
You do have a couple options:
Simply store the state in the class itself; the event handler will have access to it because it is inside the class.
Utilize a closure to do the same thing:
myButton.Click += (s, e) => ActualFunction(checkBox1.Checked);
Note that using the closure (via a lambda expression) is just hiding the details of maintaining this state (creating the class-level variables).

How do outer variables within a loop work with lambdas?

I just wrote some code that I don't quite comprehend how it works.
My question is about the local variables in the for loop which are then referenced when the radio button event occurs.
How does it track the different "versions" of these local variables and operate correctly? (i.e. the resulting radio buttons each fire an event with their corresponding value which is derived from an outer local variable)
public class RadioButtonPanel<T> : FlowLayoutPanel
{
public RadioButtonPanel()
{
foreach (object value in Enum.GetValues(typeof(T)))
{
string name = Enum.GetName(typeof(T), value);
var radioButton = new RadioButton { Text = name };
radioButton.CheckedChanged += (s, e) =>
{
if (radioButton.Checked && this.Selected != null)
Selected((T)Enum.Parse(typeof(T), name));
};
this.Controls.Add(radioButton);
}
}
public event SelectedEvent Selected;
public delegate void SelectedEvent(T t);
}
This is done via a Closure.
Basically you can imagine that a small class has been created for you on your behalf, that has two properties for your local variables, and a single function. When your lambda gets called, it basically news one of those up and calls the function, thus preserving the values given to it.
The C# specification actually has some really good examples of how this is done by the compiler. Specifically section 6.5.3
They're called closures, see Wikipedia: http://en.wikipedia.org/wiki/Closure_(computer_science)
Basically, they allow for the use of non-local variables within the lambdas. From what I can remember, these variables are compiled externally from your function so they can be used globally.

Pass an event as a parameter to a method [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to pass an event to a method?
Is it possible to pass an event as a parameter to a method?
For example, the following method subscribes to the event, does work, and unsubscribes from the event:
void SubscribeDoAndUnsubscribe<TElement, TEventArgs>(
IEnumerable<TElement> elements,
??? elementEvent)
where TEventArgs: EventArgs
{
EventHandler<TEventArgs> handler = (sender, e) => { /* Handle an event */ };
foreach (var element in elements)
{
// Subscribe somehow
element.elementEvent += handler
}
// Do things
foreach (var element in elements)
{
// Unsubscribe somehow
element.elementEvent -= handler
}
}
Client code:
var elements = new [] { new Button(), new Button() };
SubscribeDoAndUnsubscribe(elements, ??? /* e => e.Click */);
If it's not possible, how do I achieve the similar logic in other ways? Shall I pass pair of delegates for subscribe/unsubscribe methods?
You have in fact discovered that events are not "first class" in C#; you cannot pass around an event as data. You can pass around a delegate to a method associated with a receiver as a first-class object by making a delegate. You can pass around a reference to any variable as a (mostly) first-class object. (I say "mostly" because references to variables cannot be stored in fields, stored in arrays, and so on; they are highly restricted compared to other kinds of data.) You can pass around a type by obtaining its Type object and passing that around.
But there is no way to directly pass around as data an event, property, indexer, constructor or destructor associated with a particular instance. The best you can do is to make a delegate (or pair of delegates) out of a lambda, as you suggest. Or, obtain the reflection object associated with the event and pass that around, along with the instance.
No, unfortunately not.
If you look at Reactive Extensions, that suffers from a similar problem. Three options they use (IIRC - it's been a while since I've looked):
Pass in the corresponding EventInfo and call it with reflection
Pass in the name of the event (and the target if necessary) and call it with reflection
Pass in delegates for subscription and unsubscription
The call in the latter case would be something like:
SubscribeAndDoUnsubscribe(elements,
handler => e.Click += handler,
handler => e.Click -= handler);
and the declaration would be:
void SubscribeDoAndUnsubscribe<TElement, TEventArgs>(
IEnumerable<TElement> elements,
Action<EventHandler<TEventArgs>> subscription,
Action<EventHandler<TEventArgs>> unsubscription)
where TEventArgs: EventArgs
You're trying to get around type safety, and you can't do so without using reflection. I'll show you an even simpler example of what you're trying to do.
void DoSomethingOnSomethingElse(T obj, Action method)
{
obj.method();
}
C# doesn't work this way. How does the compiler know that all Ts have the method method? It doesn't, and can't. Similarly, not every TElement in your code will have an event Click for example.
It sounds like you just want to set a single use event handler on a set of objects. You can do this quite easily...
EventHandler handler = null;
handler = (s,e) =>
{
DoSomething(e);
var b = (Button) s;
b.Click -= handler;
}
foreach (var button in buttons)
{
button.Click += handler;
}
This, obviously, only works with buttons, but as I write this, I see Jon Skeet has shown you a more general solution, so I'll end here.

C#: What's the intended replacement for firing events from outside an object?

C# has gone to great lengths to ensure that you cannot "fire" an event, e.g.:
form.FormClosed(this, new FormClosedEventArgs(CloseReason.UserClosing));
button.Click(this, new EventArgs());
customer.AddressChanged(this, new EventArgs());
don't compile because you cannot fire an event this way.
This seems to have been a conscious decision on the part of the language designers. They seem to be intentionally trying to prevent "bad behavior".
i am trying to find the intended replacement.
Practical Example:
void ShowPopup(Form form)
{
ToolStripDropDown toolDrop = new ToolStripDropDown();
ToolStripControlHost toolHost = new ToolStripControlHost(form);
toolHost.Margin = new Padding(0);
toolDrop.Padding = new Padding(0);
toolDrop.Items.Add(toolHost);
toolDrop.Closed += toolDrop_Closed;
toolDrop.Show(screenLocation);
}
void toolDrop_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
//The form's FormClosed event doesn't fire when shown as a popup
//Fire the event manually
form.FormClosed(this, new FormClosedEventArgs(CloseReason.UserClosing));
}
If i'm not supposed to fire events from outside the object - what is the technique intended to replace it?
If there is no intended replacement for firing events, is there any article, book, or Channel 9 video that explains how i should handle this situation?
An event inside a class has often a public method used by class clients to invoke the event from outside of the class:
public class Foo {
public event FooDelegate FooEvent;
public void RaiseFoo() {
if ( FooEvent != null ) FooEvent();
}
}
If an event is missing such public "trigger", it usually means that for some reason you should not be able to raise the event from outside.
In your example, the event can of course be raised with
form.Close();
with, apart from other things, also raises the event.

Categories

Resources