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,可以使代码更加清晰、灵活和易于维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/404656.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络招商是什么意思,主要干什么工作?

互联网时代&#xff0c;网络招商&#xff0c;已经是企业不可缺少的一部分&#xff0c;这篇文章&#xff0c;详细为大家分享&#xff0c;网络招商的意思&#xff0c;以及主要工作内容&#xff01; 一、网络招商意思 就是利用互联网进行招商的一种方式&#xff0c;借助互联网的…

【深度学习】直观理解AUROC

文章目录 前言如何计算直观解释常用计算方式 前言 AUROC常用于衡量二分类分类器的性能&#xff0c;本文旨在详解该指标计算过程 如何计算 设想我们有一个分类器&#xff0c;对数据做二分类。我们设输入数据为 x x x, 预测标签为 y y y, ground-truth标签为 y ^ \hat{y} y…

docker 拉取镜像 error pulling image configuration: download failed ****

问题&#xff1a; 在安装docker后拉取镜像的时候 出现error pulling image configuration: download failed ****异常 原因&#xff1a; 由于镜像源配置的问题引起的镜像拉取异常 解决 由于网络或者其他原因导致拉取镜像请求失败&#xff0c;报错&#xff1a; error pulli…

Oracle 用户-表空间-表之间关系常用SQL

问题&#xff1a; 当某一个表数据量特别大&#xff0c;突然插入数据一直失败&#xff0c;可能是表空间不足&#xff0c;需要查看表的使用率 用户-表空间-表之间关系&#xff1a;用户可以有多个表空间&#xff0c;表空间可以有多个表&#xff0c;表只能拥有一个表空间和用户 1.…

爱心动画代码HTML5

这段代码是一个HTML5 Canvas动画&#xff0c;它通过JavaScript创建了一个动态的爱心效果。页面初始化时&#xff0c;首先定义了一些基本设置&#xff0c;如粒子数量、持续时间、速度等。然后&#xff0c;定义了Point和Particle类&#xff0c;用于处理粒子的位置和运动。接着&am…

C语言数据类型和变量

数据类型介绍 数据类型介绍 C语言提供了丰富的数据类型来描述生活中的各种数据。 使用整型类型来描述整数&#xff0c;使用字符类型来描述字符&#xff0c;使用浮点型类型来描述小数。 所谓“类型”&#xff0c;就是相似的数据所拥有的共同特征&#xff0c;编译器只有知道了数…

软件测试经典面试题,助你面试加分

Hi&#xff0c;大家好&#xff0c;进入金九银十&#xff0c;很多小伙伴有被动跳槽的打算&#xff0c;所以更新一些经典的软件测试面试题&#xff0c;希望能帮到大家&#xff01; 时间紧迫的情况下&#xff0c;如何做好测试工作&#xff1f; 对需求要明确&#xff0c;对需求的优…

iPhone如何全选删除照片:一步到位的清理指南

随着时间的推移&#xff0c;iPhone中的照片会迅速累积&#xff0c;最终可能占据大量的存储空间。无论是为了释放空间&#xff0c;还是整理照片库&#xff0c;iPhone如何全选删除照片成为许多用户的需求。然而&#xff0c;iPhone原生的“照片”应用并没有直接提供“全选删除”功…

汽车租赁管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图详细视频演示技术栈系统测试为什么选择我官方认证玩家&#xff0c;服务很多代码文档&#xff0c;百分百好评&#xff0c;战绩可查&#xff01;&#xff01;入职于互联网大厂&#xff0c;可以交流&#xff0c;共同进步。有保障的售后 代码参考数据库参…

SAIA触摸屏维修PCD7.D457VNCG03 SAIA-burgess

瑞士SAIA触摸屏维修SAIA-burgess思博控制器维修PCD7全系列。 触摸屏维修常见故障&#xff1a;黑屏、指示灯无任何显示&#xff0c;触摸屏上电无反应&#xff0c; 上电蓝屏、白屏&#xff0c;通电几分钟后屏幕变为蓝屏&#xff0c;主板故障&#xff0c;通讯时有时无&#xff0c…

LiveQing视频点播流媒体RTMP推流服务用户手册-概览:CPU使用、内存使用、在线人数、流量统计、带宽使用(Mbps)、存储使用、实时存储(MB/s)

LiveQing视频点播流媒体RTMP推流服务用户手册-概览:CPU使用、内存使用、在线人数、流量统计、带宽使用&#xff08;Mbps&#xff09;、存储使用、实时存储&#xff08;MB/s&#xff09; 1、概览1.1、CPU使用1.2、内存使用1.3、在线人数1.4、流量统计1.5、带宽使用(Mbps)1.6、存…

2024年系统集成企业数字化趋势与CRM研究报告

系统集成是一种新型的服务方式&#xff0c;是企业进行信息传递和共享的通用型智能工具&#xff0c;在企业系统优化升级、流程的打通和重构、数据的收集与分析应用&#xff0c;以及IT运维与安全保障等方面起着降低成本&#xff0c;提高效率的重要作用。 整体来看&#xff0c;近…

【乐吾乐大屏可视化组态编辑器】事件交互-场景交互

场景交互 在线使用&#xff1a;https://v.le5le.com/ 乐吾乐大屏可视化可以实现大屏页面与内嵌2d/3d场景相互通信&#xff0c;底层原理是利用了iframe通过postMessage发送消息。 下面以2d场景为例&#xff0c;实现步骤如下&#xff1a; 1. 首先配置场景2&#xff08;被嵌入…

【分享】格力手机色界G0245D 刷REC、root、 救砖、第三方rom教程和资源

开门见山 帮别人弄了一台 格力G0245D&#xff0c;把找到的资源和教程分享一下 教程 这个写的很详细了格力手机色界G0245D-Root-最简指南 不过教程里刷rec这一步漏了加上电源键&#xff0c;加上就行了。 附加参考&#xff1a;格力手机2刷机 格力手机二代刷机 GREE G0215D刷机…

STM32如何设置自动代码提示?

首先&#xff1a; 点击Window--->Preferences 进来之后在左上方输入keys&#xff0c;然后点击Keys&#xff0c;在Scheme下方那一栏中输入Content Assist 然后点击Content Assist--->在下方Binding栏中选择Tab--->Apply--->Apply and Close 设置完成&#xff0c;测…

hutool发邮件功能如何配置SMTP服务器参数?

hutool发邮件的教程指南&#xff1f;hutool发邮件性能优化方法&#xff1f; Hutool作为一个轻量级的Java工具库&#xff0c;其邮件发送功能因其简单易用而受到广泛关注。AokSend将详细介绍如何通过配置SMTP服务器参数来实现Hutool发邮件的功能。 hutool发邮件&#xff1a;优势…

为什么要用数字化营销管理平台?

数字化营销管理平台是一种利用数字技术来整合和优化营销流程的工具。它能够帮助企业更高效地进行市场推广、客户关系管理以及销售活动。 一、主要功能 1.数据整合与分析 整合多渠道数据&#xff0c;包括网站流量、社交媒体互动、电子邮件营销反馈等。通过数据分析&#xff0…

k8s高版本(1,28)部署NodePort模式下的ingress-nginx的详细过程及应用案例

文章目录 前言环境ingress安装应用案例(ingress-http案例&#xff1a; 基于名称的负载均衡) 前言 这个是nodeport模式下的&#xff0c;如果需要loadbalancer模式下的&#xff0c;看看博主下面以前的博客 链接: k8s学习–负载均衡器matelLB的详细解释与安装 链接: k8s学习–ing…

RTA-VRTE适配Orin

RTA-VRTE适配Orin sudo minicom -w -D /dev/ttyACM0 用户名 nvidia密码 123456底下的的rj45 ,对应的是eqos_0, (本次porting使用该接口)底下的的DEBUG(USB Micro-B)串口连接(本次porting使用该接口)上侧边的rj45,对应的是mgbe0_0#设置Orin的IP和默认网卡eqos_0

【机器学习】随机森林

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、随机森林定义 随机森林&#xff08;Random Forest, RF&#xff09;是一种由 决策树 构成的 集成算法 &#xff0c;采用的是 Bagging 方法&#xff0c;他在…