RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1587684
Accepted
James Bond
James Bond
Asked:2024-07-19 22:01:02 +0000 UTC2024-07-19 22:01:02 +0000 UTC 2024-07-19 22:01:02 +0000 UTC

事件中发送者参数的目的是什么?

  • 772

我最近开始研究 C# 中的事件主题,这是窗口应用程序之前的最后一个主题。在解释事件格式化约定时,老师多次澄清,约定本身和其中的各个解决方案(特别是同一个发送者)同时存在的原因是,在其创建时既没有 LINQ 也没有 lambda在 C# 表达式中,现在在他们的帮助下,可以更有效地解决相同的问题(链接到原始来源)。

由于我正在学习的课程已经很老了(创建了大约10年),所以我有一个问题:LINQ和lambda表达式的出现是否影响了C#中设计事件的规则,以及约定本身仍然存在吗?存在于他们的设计中,或者是否可以充分使用 LINQ 和委托?)提前致谢!

c#
  • 3 3 个回答
  • 148 Views

3 个回答

  • Voted
  1. Best Answer
    VladD
    2024-07-20T20:19:55Z2024-07-20T20:19:55Z

    看。

    首先,标准事件不是在 C# 级别定义的,也不是在 WinForms 级别定义的。创建事件的准则是在 .NET 本身中发明的,它们应该适用于 .NET 支持的所有语言:C#、Visual Basic 以及 IronPython 等外来语言(这里有完整列表)。

    .NET 框架不依赖于 C#,并且无权假设您的语言将具有方便的闭包。然而,在采用该约定时,C# 还没有闭包。

    (当然,LINQ 与事件的定义无关:如果没有 LINQ,一切都会一样。)


    但在您自己的代码中,您可以根据需要定义事件,例如如下所示:

    public event Action EverythingCrashed;
    

    但要做好心理准备,习惯于按照指导方针进行定义的同事可能会大吃一惊,而你将不得不为你的决定辩护。


    C# 中是否仍然需要这个旧约定,还是应该将其视为过时的约定?有时你需要它。

    当您编写 UI 代码时,您可能会浪费资源,因为代码不经常执行。

    但是,如果您的代码级别较低,那么使用 lambda 会有一个特殊之处:捕获变量的每个 lambda 都是一个在堆上分配的新对象,会给垃圾收集器带来一点负载。对于经常或多次执行的代码来说,这可能是一个真正的问题,而且很多时候“一点”会变成“很多”。在这种情况下,您将希望以老式方式创建一个通用方法,该方法将订阅事件,并从 和 获取您的上下文(即附加信息,如示例中的月份名称sender)args。

    顺便说一下,lambda 不一定分配在堆上。如果 lambda 没有捕获任何变量,那么编译器就足够聪明,可以将其转换为方法。为了确保您的 lambda 不会捕获任何内容,他们甚至想出了一个特殊的修饰符static( static x => x * x)。但是这样的 lambda 表达式当然无法捕获状态,您必须再次使用sender/将上下文“传递”给方法args。


    应该如何正确识别事件?没有“正确”的方法;每个程序员(如果你在一个团队中工作,则每个团队)都自己决定。

    您可以遵循.NET指南,那么您的代码将会更加熟悉。您可以忽略准则并定义事件,仅提供处理程序绝对必要的信息,在这种情况下,使用事件的代码将更加方便。由您(您的团队)决定。

    如果您正在编写共享库,那么遵循指南是有意义的,以避免给用户带来意外。如果您正在编写属于较大框架一部分的代码(例如,WPF 或 WinForms 应用程序的 View 部分),则以与代码其余部分相同的方式定义事件是有意义的(即,根据到指南)。如果您编写较低级别的代码,如果您愿意,可以使用非标准方法。

    • 5
  2. Vladislav Mikkoev
    2024-07-20T13:24:59Z2024-07-20T13:24:59Z

    关于建议:

    C #允许您生成任何种类的事件,但为了软件组件兼容性,您应该遵循Microsoft制定的准则。这些准则归结为以下要求:事件处理程序必须有两个参数。第一个是对生成事件的对象的引用,第二个是类型参数,EventArgs其中包含处理程序所需的有关事件的任何附加信息。因此,.NET 兼容的事件处理程序应具有以下一般形式:

    void Обработчик(object отправитель, EventArgs данныеСобытия) {
        // код
    }
    

    отправитель是调用代码使用关键字传递的参数this。данныеСобытия类型参数EventArgs(或从它派生的类型)包含有关事件的附加信息,如果不需要,可以忽略。该类EventArgs不包含可用于将附加数据传递给处理程序的字段。该类EventArgs充当基类,从中派生出包含所有必需字段的派生类。还有EventArgs一个static字段Empty,它是一个没有数据类型的对象EventArgs。

    代码示例(在.NET 8上测试):

    class Program
    {
        static void Main(string[] args)
        {
            // Инициализация
            var stock = new Stock();
            var stockWrapper = new StockWrapper(stock);
            
            // Вызываем основной код
            stock.Price = 100M;
            stock.Price = 200M;
            stock.Price = 300M;
            
            // Не даём закрыться консоли после выполнения
            Console.ReadLine();
        }        
    }
    
    /// <summary>
    /// Событие
    /// </summary>
    /// <param name="lastPrice">Прошлая стоимость</param>
    /// <param name="newPrice">Новая стоимость</param>
    class PriceChangedEventArgs(decimal lastPrice, decimal newPrice) : EventArgs
    {
        /// <summary>
        /// Прошлая стоимость
        /// </summary>
        public decimal LastPrice { get; } = lastPrice;
        /// <summary>
        /// Новая стоимость
        /// </summary>
        public decimal NewPrice { get; } = newPrice;
    }
    
    /// <summary>
    /// Обёртка над <see cref="Stock"/>
    /// </summary>
    class StockWrapper
    {
        public StockWrapper(Stock stock)
        {
            stock.PriceChanged += Stock_PriceChanged!;
        }
       
        /// <summary>
        /// Обработчик события
        /// </summary>
        /// <param name="sender">Объект отправитель</param>
        /// <param name="e">Данные события</param>
        private void Stock_PriceChanged(object sender, PriceChangedEventArgs e)
        {
            // Выводим на консоль данные события
            Console.WriteLine($"OldPrice: {e.LastPrice} | NewPrice: {e.NewPrice}");
        }
    }
    
    class Stock
    {
        private decimal _price;
        /// <summary>
        /// Текущая стоимость
        /// </summary>
        public decimal Price
        {
            set
            {
                // Если значение не изменилось, не меняем его и не вызываем событие
                if (_price == value)
                    return;
                
                // Если изменилось, то меняем значение и создаём событие для всех подписчиков на него
                decimal lastPrice = _price;
                decimal newPrice = value;
                PriceChanged(this, new PriceChangedEventArgs(lastPrice, _price = newPrice));
            }
        }
        
        // Тип EventHandler определён так: public delegate void EventHandler<TEventArgs>(object? sender, TEventArgs e);
        public event EventHandler<PriceChangedEventArgs> PriceChanged = delegate { };
    }
    
    • 4
  3. rotabor
    2024-07-20T02:16:54Z2024-07-20T02:16:54Z

    视频中似乎是一个正常人,他说话很好,语速也很快,但他说的是暴风雪。

    1. 正如 @CrazyElf 所写,“Linq和 lambda”与 Windows 窗体控件没有直接关系。在示例中,它们用于其预期目的 - 处理枚举 ( IEnumerable)。特别是,使用这些函数,作者从字符串数组中获取了一个类型的对象数组MenuItem。
    2. (sender,args)=>CreateReport(name)- 这是一个与 Click 事件处理程序的签名匹配的 lambda,即它有两个适当类型的参数。 Studio 将显示参数的类型sender为 -object和args- EventArgs。并且没有其他选择会起作用。也就是说,它不会sender去任何地方。这些参数在 lambda 中被简单地忽略。顺便说一句,现在可以写成(_, _) => CreateReport(name)。
    3. 为了什么sender?这样事件处理程序就可以访问调用该处理程序的对象。由于处理程序是控制元素外部的方法,因此只能通过这种方式将有关该控制元素的信息传递给它。需要这些信息吗?显然这是需要的。
    4. 带有闭包的 lambda 被编译成一个特殊的类,该类具有闭包变量值的字段,以及一个方法 - lambda 本身,它引用这些字段。由于 lambda 位于Select为集合的每个元素调用的方法中monthNames,即调用 12 次,因此在运行时使用闭包变量的值创建了 12 个辅助对象,并且Click数组的每个元素的事件menuItems接收对其单个对象的 lambda 方法。也就是说,在Linqlambda 表达式的帮助下,他自动创建了处理程序。但当他创建它时MenuItemSelected,它确实成为 12 个菜单命令的唯一处理程序。处理程序从参数中获取月份的名称sender。
    // это примерно то, что он создал
    class MenuItemSelected {
        string _name;
        public MenuItemSelected(string name) { _name = name;}
        public void Lambda(object sender, EventArgs args) => CreateReport(_name);
    }
    items[0].Click += new MenuItemSelected("январь").Lambda;
    items[1].Click += new MenuItemSelected("февраль").Lambda;
    // и т. д.
    

    正确的选项是第二个,并且可以删除附加方法:

    static void MenuItemSelected(object sender, EventArgs e) => MessageBox.Show($"создаю отчёт за {(sender as MenuItem).Text}...");
    ...
    var menuItems = monthNames.Select(name =>
        {
            var menuItem = new MenuItem(name);
            menuItem.Click += MenuItemSelected;
            return menuItem;
        }).ToArray();
    
    // или то же самое, но с лямбдой
    // здесь она просто порождает анонимный метод без сюрпризов
    
    var menuItems = monthNames.Select(name =>
        {
            var menuItem = new MenuItem(name);
            menuItem.Click += (sender, _) => { MessageBox.Show($"создаю отчёт за {(sender as MenuItem).Text}..."); }
            return menuItem;
        }).ToArray();
    

    如果每个菜单命令都需要一些附加信息,则继承所有 Windows 窗体控件的 Control 类将提供一个 Tag 属性,您可以在其中记录对具有附加信息的对象的引用。

    static void MenuItemSelected(object sender, EventArgs e) { 
        var mi = sender as MenuItem;
        MessageBox.Show($"создаю отчёт за {mi.Text}...");
        var to = mi.Tag as ATargetType;
        to. ...
    }
    
    1. 在 Google 上搜索某些内容就像...您需要从文档开始。
    2. 关于大会。约定csender是OOP范式之一——封装。参见第 3 点。
    3. 当然,与这位作者一起,您将学习编程,但您很可能很难理解自己在做什么。而且视频还可以。
    4. 该视频(这是您的原始来源)严禁在7:03之后收听。关于WPF也有一场暴风雪。
    5. 顺便说一句,如果您想使用带有复选标记的菜单命令,那么您将无法Click在没有它的情况下在处理程序中更改其状态,并且无法使用 lambda 来扭曲它。sender

    PS为什么需要它EventArgs?您将看到比菜单命令更复杂的东西,例如DataGridView。让我们以CellClick事件为例,该事件在从该类派生的对象中EventArgs告诉处理程序它发生在哪个单元格上Click。

    • 3

相关问题

  • 使用嵌套类导出 xml 文件

  • 分层数据模板 [WPF]

  • 如何在 WPF 中为 ListView 手动创建列?

  • 在 2D 空间中,Collider 2D 挂在玩家身上,它对敌人的重量相同,我需要它这样当它们碰撞时,它们不会飞向不同的方向。统一

  • 如何在 c# 中使用 python 神经网络来创建语音合成?

  • 如何知道类中的方法是否属于接口?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5