当前位置: 首页 > news >正文

WPF Mvvm

了解MVVM

  1. 什么是MVVM:一种设计模式

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

  1. 为什么需要MVVM?解决什么问题?

降低耦合、独立开发、逻辑重用(Xamarin MAUI多平台应用)、可测试

响应式布局

控件交互到MVVM模式的转变

控件交互:基于控件的功能开发

MVVM模式

代码/项目结构

Models:

Views:

ViewModels:

需求:计算器,输入:A B 输出:通过按钮 A+B 的结果

控件交互写法:

XAML代码:

<StackPanel><TextBox Text="" Name="tb_1"/><TextBlock Text="+" /><TextBox Text="" Name="tb_2"/><TextBlock Text="=" /><TextBox Text="" Name="tb_3"/><Button Content="计算" Click="Button_Click"/>
</StackPanel>

C#代码:

private void Button_Click(object sender, RoutedEventArgs e)
{// 控制逻辑double.TryParse(this.tb_1.Text, out double value_1);double.TryParse(this.tb_2.Text, out double value_2);this.tb_3.Text = (value_1 + value_2) + "";
}

效果:

MVVM模式开发:

XAML代码:View代码

在XAML或内部cs文件中,绑定对应的ViewModel

<Window x:Class="XH.MvvmLesson.Mvvm.MvvmWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:XH.MvvmLesson.Mvvm"mc:Ignorable="d"Title="MvvmWindow" Height="450" Width="800"><!--绑定ViewModel--><Window.DataContext><local:LogicClass /></Window.DataContext><StackPanel><TextBox Text="{Binding _model.Value1}" /><TextBlock Text="+" /><TextBox Text="{Binding _model.Value2}" /><TextBlock Text="=" /><TextBox Text="{Binding _model.Value3}" /><Button Content="计算" Command="{Binding BtnCommand}" CommandParameter="BtnParameter"/><Button Content="检查状态" Command="{Binding BtnCheckCommand}" /></StackPanel>
</Window>

ViewModel代码:

创建实例,绑定View

创建Command对象,绑定事件

namespace XH.MvvmLesson.Mvvm
{public class LogicClass{public DataModel _model { get; set; } = new DataModel();public Command BtnCommand { get; set; }public Command BtnCheckCommand { get; set; }public LogicClass(){BtnCommand = new Command(DoLogic, CanDoLogic);BtnCheckCommand = new Command(DoCheck);_model.PropertyChanged += _model_PropertyChanged;}// 属性变化事件private void _model_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e){BtnCommand.RaiseCanExecuteChanged();}// 检查按钮private void DoCheck(object obj){BtnCommand.RaiseCanExecuteChanged();}// 控制逻辑private void DoLogic(object obj){_model.Value3 = _model.Value1 + _model.Value2;}// 是否可以启用按钮private bool CanDoLogic(object obj){return _model.Value1 > 0 && _model.Value2 > 0;}}
}

Model代码:

INotifyPropertyChanged:使用页面通知属性,在属性set的时候,调用

using System.ComponentModel;namespace XH.MvvmLesson.Mvvm
{// 针对功能所提供的数据模型public class DataModel : INotifyPropertyChanged{private double _value1;public double Value1{get { return _value1; }set{_value1 = value;PropertyChanged?.Invoke(this,new PropertyChangedEventArgs("Value1"));}}private double _value2;public double Value2{get { return _value2; }set{_value2 = value;PropertyChanged?.Invoke(this,new PropertyChangedEventArgs("Value2"));}}private double _value3;public double Value3{get { return _value3; }set{_value3 = value;// 在系统内广而告之 告诉页面上哪个对象关注了这个实例里的这个属性的,赶紧更新结果PropertyChanged?.Invoke(this,new PropertyChangedEventArgs("Value3"));}}// 做信息发布的对象 需要执行这一下这个对象public event PropertyChangedEventHandler? PropertyChanged;}
}

事件Command代码:ICommand

需要继承ICommand接口

CanExecute:判断是否需要启用这个事件,等同于IsEnabled

在界面加载的时候调用一次,在每次执行之前再调用一次

Execute:执行事件方法

parameter:界面的CommandParameter属性来传值

using System.Windows.Input;namespace XH.MvvmLesson.Mvvm
{public class Command : ICommand{// 判断绑定的当前命令实例的对象 是否可用// 比如按钮是否可以执行下面的逻辑public event EventHandler? CanExecuteChanged;// 外部调用public void RaiseCanExecuteChanged(){// 通知方法使用是否可以触发 通知状态检查CanExecuteChanged?.Invoke(this, EventArgs.Empty);}// 作用,触发一个时机 切换调用方的状态是否可用public bool CanExecute(object? parameter){return _canExecute?.Invoke(parameter) != false;}// 参数 返回值private Func<object?, bool> _canExecute;// 绑定了当前命令实例的对象的执行逻辑// 等同于 Button的Click 事件// parameter:界面的CommandParameter属性来传值public void Execute(object? parameter){// 委托 DoExeute?.Invoke(parameter);}public Action<object> DoExeute { get; set; }public Command(Action<object> action, Func<object?, bool> func = null){DoExeute = action;_canExecute = func;}}
}

MVVM绑定模式下的信息交互

数据类型:INotifyPropertyChanged接口
class Class1 : INotifyPropertyChanged
{public event PropertyChangedEventHandler? PropertyChanged;
}
行为动作:ICommand接口
class Class1 : ICommand
{// 调用这个方式的时候 又调用 CanExecute 方法public event EventHandler? CanExecuteChanged;// 外部调用public void RaiseCanExecuteChanged(){// 通知方法使用是否可以触发 通知状态检查CanExecuteChanged?.Invoke(this, EventArgs.Empty);}// 作用,触发一个时机 切换调用方的状态是否可用public bool CanExecute(object? parameter){throw new NotImplementedException();}// 绑定了当前命令实例的对象的执行逻辑// 等同于 Button的Click 事件// parameter:界面的CommandParameter属性来传值public void Execute(object? parameter){throw new NotImplementedException();}
}

扩展:修改数据格式,把每次的算法记录成一个表格,并且添加删除按钮可以删除

主要是修改表格的数据模板,并且每个数据增加个按钮 进行移除,并且传入当前集合,集合通知属性

部分XAML代码:

 <Grid><Grid.ColumnDefinitions><ColumnDefinition /><ColumnDefinition /></Grid.ColumnDefinitions><StackPanel><TextBox Text="{Binding _model.Value1,UpdateSourceTrigger=PropertyChanged}" /><TextBlock Text="+" /><TextBox Text="{Binding _model.Value2,UpdateSourceTrigger=PropertyChanged}" /><TextBlock Text="=" /><TextBox Text="{Binding _model.Value3}" /><Button Content="计算" Command="{Binding BtnCommand}" CommandParameter="BtnParameter"/><Button Content="检查状态" Command="{Binding BtnCheckCommand}" /></StackPanel><ListBox Grid.Column="1" ItemsSource="{Binding ResultList}" Name="lb"><ListBox.ItemTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding}" /><!--<TextBlock Text="{Binding State}" />--><!--只写个Binding 是绑定当前数据源--><Button Content="删除" CommandParameter="{Binding}"Command="{Binding DataContext.BtnDelCommand,RelativeSource={RelativeSource AncestorType=Window}}" /></StackPanel></DataTemplate></ListBox.ItemTemplate></ListBox></Grid>

只写个Binding 是绑定当前数据源

部分C#代码:

ObservableCollection:集合通知属性

// ObservableCollection:集合通知属性,代替List 可以通知界面修改数据
//public ObservableCollection<ResultModel> ResultList { get; set; } = new ObservableCollection<ResultModel>();
public ObservableCollection<string> ResultList { get; set; } = new ObservableCollection<string>();public Command BtnDelCommand { get; set; }public MainViewModel()
{BtnDelCommand = new Command(DoDel);
}// 删除按钮
private void DoDel(object obj)
{ResultList.Remove((string)obj);
}// 控制逻辑
private void DoLogic(object obj)
{_model.Value3 = _model.Value1 + _model.Value2;// 如果希望通知子项的变化(这个集合中的子项的增减)// 需要实现INotifyCollectionChanged接口,进行子项变化通知// 框架提供了通知集合对象ObservableCollection//ResultList.Add(new ResultModel//{//    Msg = $"第{ResultList.Count + 1}次{_model.Value1} + {_model.Value2} = {_model.Value3}",//});ResultList.Add($"第{ResultList.Count + 1}次{_model.Value1} + {_model.Value2} = {_model.Value3}");
}

显示效果:

MVVM绑定扩展

无法绑定的对象属性

通过附加属性进行扩展

案例:绑定ScottPlot,实现图标动态刷新

View:

<Window x:Class="XH.MvvmPattern.Views.ScottPlotWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:XH.MvvmPattern.Views"xmlns:vm="clr-namespace:XH.MvvmPattern.ViewModels"xmlns:b="clr-namespace:XH.MvvmPattern.Base"mc:Ignorable="d"Title="ScottPlotWindow" Height="450" Width="800"><Window.DataContext><vm:ScottPlotViewModel /></Window.DataContext><Grid><WpfPlot Name="wpf_plot" b:ScottPlotExtension.Values="{Binding Datas}"/></Grid>
</Window>

Model:由于数据很少,数据写在ViewModel 里面

ViewModel:

using ScottPlot;
using System.Collections.ObjectModel;
using System.Windows;namespace XH.MvvmPattern.ViewModels
{public class ScottPlotViewModel{public ObservableCollection<double> Datas { get; set; } public ScottPlotViewModel(){Datas = new ObservableCollection<double>(DataGen.RandomWalk(new Random(), 10));// 实时监控 持续获取数据 Task.Run(async () =>{while (true){await Task.Delay(1000);Application.Current.Dispatcher.BeginInvoke(() =>{Datas.Add(new Random().NextDouble());Datas.RemoveAt(0);});}});}}
}

附加属性:

using ScottPlot;
using System.Collections.ObjectModel;
using System.Windows;namespace XH.MvvmPattern.Base
{public class ScottPlotExtension{// 依赖附加属性// 这个属性里面可以知道绑定的属性 以及被附加的对象public static ObservableCollection<double> GetValues(DependencyObject obj){return (ObservableCollection<double>)obj.GetValue(ValuesProperty);}public static void SetValues(DependencyObject obj, int value){obj.SetValue(ValuesProperty, value);}// Using a DependencyProperty as the backing store for Values.  This enables animation, styling, binding, etc...public static readonly DependencyProperty ValuesProperty =DependencyProperty.RegisterAttached("Values",typeof(ObservableCollection<double>),typeof(ScottPlotExtension),new PropertyMetadata(null, new PropertyChangedCallback(OnValuesChanged)));// 当给Value赋值的时候 才会触发private static void OnValuesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){var plt = d as WpfPlot;var newPlt = plt.Plot;var new_list = (ObservableCollection<double>)e.NewValue;var signal = newPlt.AddSignal(new_list.ToArray());// 对集合实例进行子项增减的时候 进行回调new_list.CollectionChanged += (s, e) =>{// 由于数组长度不能变 所以是ViewModel中,长度变为10的事件中,再触发,加个判断if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove){signal.Update(new_list.ToArray());plt.Refresh();}};plt.Refresh();}}
}

注意:附加属性中的属性改变事件,只有在Value赋值的时候才会触发,但是List中间值改变不会触发,

所以需要再此事件中写集合的值改变事件:CollectionChanged

无法绑定的动作事件

单击鼠标左键 :LeftClick

双击鼠标左键:LeftDoubleClick

单击鼠标中键 :MiddleClick

双击鼠标中键:MiddleDoubleClick

单击鼠标右键:RightClick

双击鼠标右键:RightDoubleClick

不执行任何操作:None

旋转鼠标滚轮:WheelClick

键盘和鼠标常用方法:InputBindings

<Border.InputBindings><MouseBinding Command="{Binding BtnCommand}" MouseAction="LeftClick" /><KeyBinding Command="{Binding BtnCommand}" Key="Enter" />
</Border.InputBindings>
事件转命令

Behavior

第一步:下载NuGet 包

第二步:引入命名空间

xmlns:b="http://schemas.microsoft.com/xaml/behaviors"

第三步:使用:

EventName:绑定的事件名称

Command:绑定

 <Border Height="40" Background="Orange"><b:Interaction.Triggers><b:EventTrigger EventName="MouseLeftButtonDown"><b:InvokeCommandAction Command="{Binding BtnCommand}" /></b:EventTrigger></b:Interaction.Triggers></Border>

自定义实现

思路:就是写一个附加属性,然后同过一个类,传入事件名称和事件Command,然后同过反射反射到对应的控件事件上,进行调用即可。

C#代码:

using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;namespace XH.MvvmPattern.Base
{// 任意事件绑定public class EventExtension{// 解决:页面至少需要传递两个信息:事件名陈、命令,可以将这两个信息进行类的打包 EventCommand// 通过EventCommand的实例进行相关信息的获取(事件名称、命令)// 然后进行反射事件的委托挂载,在执行命令过程public static EventCommand GetEventTarget(DependencyObject obj){return (EventCommand)obj.GetValue(EventTargetProperty);}public static void SetEventTarget(DependencyObject obj, EventCommand value){obj.SetValue(EventTargetProperty, value);}// Using a DependencyProperty as the backing store for EventTarget.  This enables animation, styling, binding, etc...public static readonly DependencyProperty EventTargetProperty =DependencyProperty.RegisterAttached("EventTarget", typeof(EventCommand), typeof(EventExtension), new PropertyMetadata(null,new PropertyChangedCallback(OnEventTargetChanged)));private static void OnEventTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){if (d is UIElement control){var eventCommand = e.NewValue as EventCommand;if (eventCommand == null) return;string eventName = eventCommand.EventName;// 获取控件类型	Type controlType = control.GetType();bool isEvent = IsEvent(eventName, controlType);if (isEvent){EventInfo eventInfo = controlType.GetEvent(eventName);if (eventInfo != null){// 动态挂载事件MethodInfo methodInfo = typeof(EventExtension).GetMethod("OnEventRaised", BindingFlags.NonPublic | BindingFlags.Static);Delegate handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, methodInfo);eventInfo.AddEventHandler(control, handler);control.SetValue(EventTargetProperty, eventCommand);}}}}// 挂载事件 事件转命令 private static void OnEventRaised(object sender, EventArgs e){DependencyObject control = sender as DependencyObject;if (control == null) return;EventCommand eventCommand = GetEventTarget(control);if (eventCommand == null) return;ICommand command = eventCommand.Command;// 如果Command 获取为null,有可能是因为EventCommand对象的继承有问题,需要继承Animatableif (command != null && command.CanExecute(eventCommand.CommandParameter)){command.Execute(eventCommand.CommandParameter);}}// 判断是不是事件private static bool IsEvent(string eventName, Type controlType){// 获取控件类型的所有事件EventInfo[] events = controlType.GetEvents();// 检查是否存在与给定名称匹配的事件foreach (EventInfo eventInfo in events){if (eventInfo.Name.Equals(eventName, StringComparison.OrdinalIgnoreCase)){return true;}}return false;}}public class EventCommand : Animatable{public string EventName { get; set; }// 事件绑定public ICommand Command{get { return (ICommand)GetValue(CommandProperty); }set { SetValue(CommandProperty, value); }}// Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...public static readonly DependencyProperty CommandProperty =DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommand), new PropertyMetadata(null));// 参数public object CommandParameter{get { return (object)GetValue(CommandParameterProperty); }set { SetValue(CommandParameterProperty, value); }}// Using a DependencyProperty as the backing store for CommandParameter.  This enables animation, styling, binding, etc...public static readonly DependencyProperty CommandParameterProperty =DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventCommand), new PropertyMetadata(0));protected override Freezable CreateInstanceCore(){return (Freezable)Activator.CreateInstance(GetType());}}
}

注意:如果在挂载的时候,发现Command是null,或者绑定的时候目标源在FrameworkElement 或 FrameworkContentElement上的时候,需要继承Animatable类,也不是继承接口:ICommand

主要是仿照微软提供的behaviors进行编写,这个方法也是继承Animatable才能实现Command 传递

ContextMenu绑定事件处理

XAML代码:原因:ContextMenu在窗体之上,不在窗体之内,所以找不到窗体的数据源,需要明确指定数据源

<Window.Resources><vm:MainViewModel x:Key="mvm" />
</Window.Resources>
<Window.DataContext><!--<vm:MainViewModel />--><StaticResource ResourceKey="mvm" />
</Window.DataContext>
………………
<StackPanel.ContextMenu><ContextMenu><!--第一种方式 Source={x:Reference Name=lb}--><!--<MenuItem Header="删除" CommandParameter="{Binding}" Command="{Binding DataContext.BtnDelCommand,Source={x:Reference Name=lb}}"/>--><!--第二种方式 把数据源当做资源调用 Source 即可--><!--原因:ContextMenu在窗体之上,不在窗体之内,所以找不到窗体的数据源,需要明确指定数据源注意:命令的数据源的指定--><MenuItem Header="删除" CommandParameter="{Binding}" Command="{Binding BtnDelCommand,Source={StaticResource mvm}}"/></ContextMenu>
</StackPanel.ContextMenu>
自定义控件的事件绑定:

C#代码:直接在代码中写依赖绑定属性即可,在有需要的地方调用Execute

public partial class DateTimePicker :UserControl, INotifyPropertyChanged{// 命令属性public ICommand SelectedCommand{get { return (ICommand)GetValue(SelectedCommandProperty); }set { SetValue(SelectedCommandProperty, value); }}// Using a DependencyProperty as the backing store for SelectedCommand.  This enables animation, styling, binding, etc...public static readonly DependencyProperty SelectedCommandProperty =DependencyProperty.Register("SelectedCommand", typeof(ICommand), typeof(DateTimePicker), new PropertyMetadata(null));public object SelectedCommandParameter{get { return (object)GetValue(SelectedCommandParameterProperty); }set { SetValue(SelectedCommandParameterProperty, value); }}// Using a DependencyProperty as the backing store for SelectedCommandParameter.  This enables animation, styling, binding, etc...public static readonly DependencyProperty SelectedCommandParameterProperty =DependencyProperty.Register("SelectedCommandParameter", typeof(object), typeof(DateTimePicker), new PropertyMetadata(null));…………………………// 在需要调用的地方 调用此事件即可private void Button_Click(object sender, RoutedEventArgs e){// 命令的执行SelectedCommand?.Execute(SelectedCommandParameter);}}

XAML代码:

<c:DateTimePicker VerticalAlignment="Top" HorizontalAlignment="Center" SelectedCommand="{Binding SeletedCommand}" SelectedCommandParameter="{Binding}"/>

MVVM分层逻辑相关问题

VM逻辑中弹窗操作

需求:应用中逻辑处理过程中需要打开一个子窗口,作为Dialog窗口进行信息显示

利用一个第三方对象WindowProvider,允许View层进行窗口对象的注册 ,允许ViewModel层进行窗口对象的调用请求

过程中涉及两方的数据传递

第三方对象WindowProvider:

using System.Windows;namespace XH.Mvvm.Base
{public class WindowProvider{static Dictionary<string, WindowInfo> types = new Dictionary<string, WindowInfo>();// 收购窗口// 允许自定义名字 key,不自定义就是窗体名public static void Register<T>(string key = "",Window owner = null){if (string.IsNullOrEmpty(key))key = typeof(T).Name;if (!types.ContainsKey(key))types.Add(key, new WindowInfo { WinType = typeof(T),OwnerType = owner });}// 出售public static bool ShowDialog(string key,object dataContext){Type type = null;if (types.ContainsKey(key)){type = types[key].WinType;}if (type != null){// 同过反射创建新的对象Window win = (Window)Activator.CreateInstance(type);// 设置窗口所有者 当任务栏打开大窗口的时候,小窗口带出win.Owner = types[key].OwnerType;win.DataContext = dataContext;bool state = (bool)win.ShowDialog();return state;}elsethrow new Exception("没有找到对应的弹窗对象");}}class WindowInfo{public Type WinType { get; set; }public Window OwnerType { get; set; }}
}

View地方调用:

 public MainWindow(){InitializeComponent();WindowProvider.Register<SubWindow>(owner: this);}

ViewModel地方调用:

public MainViewModel()
{BtnCommand = new Command(obj =>{// Btn的执行逻辑 打开子窗口// 因为不能View-->ViewModel-->View 来回调用,此方法不可取//new SubWindow().ShowDialog();SubViewModel subViewModel = new SubViewModel();subViewModel.Value = Value;if (WindowProvider.ShowDialog("SubWindow", subViewModel)){// 获取到子窗口返回的Value属性this.Value = subViewModel.Value;}else { }// 如何从子窗口返回到主窗口:SUbViewModel --> MainViewModel// 1、子窗口打开后,编辑完成,保存按钮、取消按钮// 2、最终数据不在子窗口处理,返回到主VM再处,保存、回退});
}

显示:能够顺利显示和传值

逻辑:

  1. 收购
    1. 定义一个字典,然后记录需要弹出的窗口信息(窗口名称,父窗口)和key,用key和信息进行绑定,然后调用的时候用key调用窗口信息。
  1. 出售
    1. 如果存在这个key,然后同过反射把字典中的窗口信息反射出来给Window,然后进行设置Window的属性,并且弹窗,同过DialogResult属性来判断是否需要写入信息。
    2. 出售(调用)的时候,把需要传入的Model通过参数传进来。
  1. 调用
    1. 通过出售的时候返回的bool信息,来判断是否需要保存下来返回的值
任意对象间逻辑调用

委托对象的管理,被动方进行委托方法的注册,主动方进行委托方法的请求调用

核心 :委托对象的使用

ActionManager类:


namespace XH.Mvvm.Base
{public class ActionManager{static Dictionary<string, Delegate> types = new Dictionary<string, Delegate>();public static void Register<T>(Action<T> action, string key){if (!types.ContainsKey(key))types.Add(key, action);}  public static void Register<T>(Func<T> action, string key){if (!types.ContainsKey(key))types.Add(key, action);}public static void Invoke<T>(string key, T arg){if (types.ContainsKey(key))((Action<T>)types[key]).Invoke(arg);}public static T InvokeWithResult<T>(string key){if (types.ContainsKey(key))return (types[key] as Func<T>).Invoke();return default(T);}}
}

A窗体进行订阅:

public MainViewModel()
{// 订阅的过程,ActionManager.Register<object>(new Action<object>(DoAction), "AAA");ActionManager.Register<object>(new Func<object>(DoFunc), "BBB");}private object DoFunc(){return this.Value;}private void DoAction(object obj){}

B窗体进行调用:

public SubViewModel()
{SubmitCommand = new Command(obj =>{// 发布的动作// B窗口传递给A窗口值ActionManager.Invoke<object>("AAA",Value);// A窗口传递给B窗口值var v = ActionManager.InvokeWithResult<object>("BBB");});
}

逻辑:

  1. 注册
    1. 注册一个委托key字典,然后同过key和方法进行注册,发布的时候同过key进行搜索委托事件执行。
    2. 可以注册Action 和 Func 方法,Action是调用的时候需要参数,Func调用的时候是返回参数
    3. 注册Func方法的时候,需要返回一个Object类型的参数,给调用方
  1. 调用/发布
    1. 同过Key进行调用,但是需要传入参数,如果是Action的话,需要传给注册方一个object参数,在注册方的方法中。
    2. 如果是Func的话,返回一个参数,是注册方返回给掉用方的参数

注意:Func 和 Action 的区别

Func
  • 定义:Func是一个泛型委托,用于表示可以带有参数并且返回值的方法。Func委托可以有多个输入参数,但只能有一个返回值。
  • 用途:通常用于需要执行一个操作并返回结果的情况。例如,你可能需要一个方法来检查某个字符串是否符合特定的格式,并返回一个布尔值表示检查结果。
  • 语法示例Func<T, TResult> 表示一个接受一个类型为T的参数并返回一个类型为TResult的结果的委托。Func<int, string, bool> 表示一个接受一个int类型和一个string类型参数,并返回一个bool类型结果的委托。
Action
  • 定义:Action是另一个泛型委托,但它不返回任何值(即返回类型为void)。Action委托可以有多个输入参数,但没有返回值。
  • 用途:适用于那些执行操作但不需要返回结果的情况。例如,你可能需要一个方法来打印日志信息,或者更新某个对象的状态,这些操作都不需要返回值。
  • 语法示例Action<T> 表示一个接受一个类型为T的参数但不返回任何结果的委托。Action<int, string> 表示一个接受一个int类型和一个string类型参数,但不返回任何结果的委托。
Func和Action的主要区别归纳

特性

Func

Action

返回值

有返回值

无返回值(void)

参数数量

可以有0到多个参数

可以有0到多个参数

用途

执行操作并返回结果

执行操作但不返回结果

语法示例

Func<T, TResult>

Action<T>

总结

Func和Action是编程中用于表示不同类型方法的泛型委托。Func用于表示需要返回值的方法,而Action用于表示不需要返回值的方法。它们的使用取决于你的具体需求,即在执行某个操作时是否需要返回结果。通过合理使用Func和Action,可以使代码更加清晰、灵活和易于维护。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • MySQL集群+Keepalived实现高可用部署
  • Hooks 「 useImperativeHandle 」子组件向父组件暴露方法
  • Dockerfile常用指令详解
  • 在NVIDIA jetson中使用jetson-ffmpeg调用硬件编解码加速处理
  • TCP的连接建立及报文段首部格式
  • ESP32-IDF 在 Ubuntu 下的配置
  • 【xilinx】Vivado 成功运行Ubuntu需要哪些 文件?
  • 微软RDL远程代码执行超高危漏洞(CVE-2024-38077)漏洞检测排查方式
  • JavaSE基础(12)——文件、递归、IO流
  • 未知单播泛洪原因
  • 日志审计Graylog 使用教程-kafka收取消息
  • 【数据结构】一篇讲清楚什么是堆? 带图食用超详细~
  • go-zero接入skywalking链路追踪
  • C语言高手参考手册:函数进阶技巧
  • C++基础面试题 | C++中值传递和引用传递的区别?
  • [ JavaScript ] 数据结构与算法 —— 链表
  • CSS 专业技巧
  • Essential Studio for ASP.NET Web Forms 2017 v2,新增自定义树形网格工具栏
  • FastReport在线报表设计器工作原理
  • JavaScript的使用你知道几种?(上)
  • mockjs让前端开发独立于后端
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • PermissionScope Swift4 兼容问题
  • Sass 快速入门教程
  • Zepto.js源码学习之二
  • 百度贴吧爬虫node+vue baidu_tieba_crawler
  • 编写高质量JavaScript代码之并发
  • 从零开始学习部署
  • 飞驰在Mesos的涡轮引擎上
  • 规范化安全开发 KOA 手脚架
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 区块链技术特点之去中心化特性
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 使用Swoole加速Laravel(正式环境中)
  • 想使用 MongoDB ,你应该了解这8个方面!
  • 译自由幺半群
  • 用mpvue开发微信小程序
  • 优化 Vue 项目编译文件大小
  • 3月27日云栖精选夜读 | 从 “城市大脑”实践,瞭望未来城市源起 ...
  • 带你开发类似Pokemon Go的AR游戏
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • 正则表达式-基础知识Review
  • #565. 查找之大编号
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • #LLM入门|Prompt#3.3_存储_Memory
  • #NOIP 2014#day.2 T1 无限网络发射器选址
  • #预处理和函数的对比以及条件编译
  • $LayoutParams cannot be cast to android.widget.RelativeLayout$LayoutParams
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (2)nginx 安装、启停
  • (C)一些题4
  • (k8s)Kubernetes 从0到1容器编排之旅
  • (ZT)出版业改革:该死的死,该生的生
  • (二)斐波那契Fabonacci函数