将额外的参数传递给事件处理程序?

68

假设我想在分配事件处理程序时传递一些额外的数据。请考虑以下代码:

private void setup(string someData)
{
     Object.assignHandler(evHandler);
}

public void evHandler(Object sender)
{
    // need someData here!!!
}

我该如何将someData传入我的evHandler方法中?


我在这个问题中有一个更标准的解决方案,第一个答案:https://dev59.com/h2Yq5IYBdhLWcg3w8lNo#14058441 - Saw
10个回答

56
private void setup(string someData)
{
     Object.assignHandler((sender) => evHandler(sender,someData));
}
public void evHandler(Object sender, string someData)
{
    // need someData here!!!
}

2
这是一个很好的解决方案,但如果EventHandler已经有TArgs怎么办?例如:+= new EventHandler<AsyncCompletedEventArgs>(evHandler)? - Roast
1
谢谢,这解决了我遇到的问题,但我是C#的新手,您能否解释一下这个语言结构是什么或给我一个关键词以便我可以搜索解释? - Tom Smilack
我在这个问题中有一个更标准的解决方案,第一个答案:https://dev59.com/h2Yq5IYBdhLWcg3w8lNo#14058441 - Saw
嗯,我明白了,那么问题在于将另一个问题标记为此问题的重复,但您的答案非常适用于这种特定情况,我很快地阅读了答案,但没有看到事件参数类,不过还是谢谢您的回复,我会在能够时点赞该问题 :) - Saw
cn.InfoMessage += ((s, e) => { Console.WriteLine("[" + DBName + "] [备份] " + e.Message); }); 这可能是大多数人正在寻找的。其中,DBName是一个本地变量,它在作用域内。 - Shiroy
显示剩余3条评论

35

我在理解@spender上面的例子时遇到了困难,特别是这句话:Object.assignHandler((sender) => evHandler(sender,someData)); 因为没有类似于 Object.assignHandler 的东西。所以我进行了更多的谷歌搜索,并找到了这个示例。Peter Duniho的答案让我恍然大悟(这不是我的成果):

省略内容

通常的方法是使用一个匿名方法和一个具有修改后签名的事件处理程序。例如:

void Onbutton_click(object sender, EventArgs e, int i) { ... }

button.Click += delegate(object sender, EventArgs e) 
{ Onbutton_click(sender, e, 172); };

当然,你不必传入172,甚至可以将第三个参数设为int的任何值. :)

/snip

使用该示例,我能够使用lambda符号将两个自定义的ComboBoxItem对象传递给Timer.Elapsed事件:

simulatorTimer.Elapsed +=
(sender, e) => onTimedEvent(sender, e,
(ComboBoxItem) cbPressureSetting.SelectedItem,
(ComboBoxItem) cbTemperatureSetting.SelectedItem);

然后进入它的处理程序:

static void onTimedEvent(object sender, EventArgs e, ComboBoxItem pressure, ComboBoxItem temperature)
    {
        Console.WriteLine("Requested pressure: {0} PSIA\nRequested temperature: {1}° C", pressure, temperature);
    }

这不是以上示例中的新代码,但它演示了如何解释它们。希望像我这样的人会发现它有用,这样他们就不必像我一样花费数小时来理解这个概念。

这段代码在我的项目中有效(除了一个非线程安全的异常与ComboBoxItem对象相关,我认为这并不影响示例的工作原理)。我正在解决这个问题。


2
唯一的问题是,如果你想要取消订阅事件(很可能你应该这样做),你将通过一个匿名委托来取消订阅,而这是不应该做的 - 参见这个相关问题的答案 - jbyrd

19

捕获变量:

private void setup(string someData)
{
    Object.assignHandler((sender,args) => {
        evHandler(sender, someData);
    });
}

public void evHandler(Object sender, string someData)
{
    // use someData here
}

或者(C# 2.0的替代方案):

    Object.assignHandler((EventHandler)delegate(object sender,EventArgs args) {
        evHandler(sender, someData);
    });

3
被捕获的变量和闭包是相同的概念吗? - Matt
3
是的,捕获变量是词法闭包。 - Marc Gravell
请澄清一下,第一个解决方案对于C# .NET 2.0不可行吗? - Andy Hin
1
@whydna 第一种解决方案需要使用C# 3.0或更高版本;有一个微妙的语法补充。 - Marc Gravell

4
您可以尝试这样做:
string yourObject;

theClassWithTheEvent.myEvent += (sender, model) =>
{
 yourObject = "somthing";
}

1

由于我的类似问题被标记为重复,所以我想在这里添加一个答案,因为它不允许我在我的问题上进行回答。

class Program
    {
        delegate void ComponentEventHandler(params dynamic[] args);

        event ComponentEventHandler onTest;

        static void Main(string[] args)
        {  
            Program prg = new Program();

            // can be bound to event and called that way
            prg.onTest += prg.Test;
            prg.onTest.Invoke("What", 5, 12.0);

            Console.ReadKey();
        }

        public void Test(params dynamic[] values)
        {
            // assign our params to variables
            string name = values[0];
            int age = values[1];
            double value = values[2];

            Console.WriteLine(name);
            Console.WriteLine(age);
            Console.WriteLine(value);
        }
    }

0

这是我的一行代码解决方案,可以向计时器处理程序传递额外的参数。

private void OnFailed(uint errorCode, string message)
{
    ThreadPoolTimer.CreateTimer((timer) => {
    UI.ErrorMessage = string.Format("Error: 0x{0:X} {1}", errorCode, message);
    }, System.TimeSpan.FromMilliseconds(100));
}

0

在我的同事友情帮助之前,我在互联网上搜索了很久,感觉自己很蠢。Brackets是EventHandler的解决方案。

例如:

event EventHandler<(int, bool)> EventName;

然后使用以下代码进行获取:

private void Delegate_EventName(object sender, (int, bool) e)

你可以随后访问信息:

var temp = e.Item1;<br>
var temp2 = e.Item2;<br>

或者您可以按照参数的预期添加名称,并通过e进行调用:

private void Delegate_EventName(object sender, (int num, bool val) e)

然后你可以访问这些信息:

var temp = e.num;
var temp2 = e.val;

0

这个解决方案提供了一种方法,可以在仍然允许取消订阅的情况下向事件处理程序传递额外的参数:

在我的示例的Subscribe()函数中,我创建了一个Action,它调用一个lambda函数,该函数为我的事件处理程序提供了事件参数和额外的参数。然后,我将此Action存储在字典中。当我想要取消订阅时,我可以使用存储的Actions来执行。

这很有效,我在取消订阅之前和之后读取了侦听器的长度,它确实减少了 - 您可以再次无问题地取消订阅。

    public class Player
    {
        public Action<JumpInfo> OnJump;
    }

    public class PlayerJumpListener
    {
        public List<Player> MyPlayerList;
        private Dictionary<Player, Action<JumpInfo>> _jumpActionsByPlayer = new Dictionary<Player, Action<JumpInfo>>();

        private void Subscribe()
        {
            foreach (Player player in MyPlayerList)
            {
                Action<JumpInfo> playerJumpAction = (jumpInfo) => HandlePlayerJump(jumpInfo, player);
                player.OnJump += playerJumpAction;

                _jumpActionsByPlayer.Add(player, playerJumpAction);
            }
        }

        private void Unsubscibe()
        {
            foreach (KeyValuePair<Player, Action<JumpInfo>> kvp in _jumpActionsByPlayer)
            {
                kvp.Key.OnJump -= kvp.Value;
            }
        }

        private void HandlePlayerJump(JumpInfo jumpInfo, Player player)
        {
            // player jumped
        }
    }

0

嗯,最简单的方法是将someData作为成员变量:

public class MyClass
{
    private string _eventData;

    private void setup(string someData) 
    {
       _eventData = someData;
       Object.assignHandler(evHandler);
    }

    public void evHandler()
    {
        // do something with _eventData here
    }
}

我不确定那是最好的方法,但这真的取决于事件类型、对象等。


2
我在考虑这种方法,但是setup可能会被多次调用,并且使用不同的someData。每个处理程序的数据应该是唯一的。 - Andy Hin
在这种情况下,spender或Marc的解决方案更好。 - CodingGorilla

0

你可以基于Object创建一个具有额外属性的自定义对象:

class CustomObject : Object
{
    public string SomeData;
}

private void setup(string someData)
{
    CustomObject customObject = new CustomObject { SomeData = someData };
    CustomObject.assignHandler(evHandler);
}

public void evHandler(Object sender)
{
    string someData = ((CustomObject)sender).SomeData;
}

如果在初始化之后数据不应再更改,您还可以添加自定义构造函数,例如。


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