WPF Mvvm
了解MVVM
- 什么是MVVM:一种设计模式
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
- 为什么需要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再处,保存、回退});
}
显示:能够顺利显示和传值

逻辑:
- 收购
-
- 定义一个字典,然后记录需要弹出的窗口信息(窗口名称,父窗口)和key,用key和信息进行绑定,然后调用的时候用key调用窗口信息。
- 出售
-
- 如果存在这个key,然后同过反射把字典中的窗口信息反射出来给Window,然后进行设置Window的属性,并且弹窗,同过DialogResult属性来判断是否需要写入信息。
- 出售(调用)的时候,把需要传入的Model通过参数传进来。
- 调用
-
- 通过出售的时候返回的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");});
}
逻辑:
- 注册
-
- 注册一个委托key字典,然后同过key和方法进行注册,发布的时候同过key进行搜索委托事件执行。
- 可以注册Action 和 Func 方法,Action是调用的时候需要参数,Func调用的时候是返回参数
- 注册Func方法的时候,需要返回一个Object类型的参数,给调用方
- 调用/发布
-
- 同过Key进行调用,但是需要传入参数,如果是Action的话,需要传给注册方一个object参数,在注册方的方法中。
- 如果是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和Action是编程中用于表示不同类型方法的泛型委托。Func用于表示需要返回值的方法,而Action用于表示不需要返回值的方法。它们的使用取决于你的具体需求,即在执行某个操作时是否需要返回结果。通过合理使用Func和Action,可以使代码更加清晰、灵活和易于维护。
相关文章:
WPF Mvvm
了解MVVM 什么是MVVM:一种设计模式 设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人…...
pnpm【实用教程】2024最新版
pnpm 简介 pnpm 全称 performant npm,即高性能的 npm,由 npm/yarn 衍生而来,解决了 npm/yarn 内部潜在的 bug,极大的优化了性能,扩展了使用场景,被誉为 最先进的包管理工具 安装 pnpm npm i -g pnpm使用 pn…...
C#的前沿技术有哪些?
C#作为.NET平台的核心语言,其前沿技术主要围绕.NET生态系统的扩展和更新展开。了解C#的前沿技术对于开发者来说至关重要,因为它们代表了该语言和平台的最新发展方向和趋势。目前,C#的前沿技术主要集中在以下几个方面: 1. NET 6: …...
Vue2移动端(H5项目)项目基于vant封装图片上传组件(支持批量上传、单个上传、回显、删除、预览、最大上传数等功能)---解决批量上传问题
一、最终效果 二、参数配置 1、代码示例: <t-uploadfileList"fileList":showFileList"showFileList"showFile"showFile":showFileUrl"showFileUrl"/>2、配置参数(TUpload Attributes)继承va…...
ELK整合实战,filebeat和logstash采集SpringBoot项目日志发送至ES
文章目录 ELK整合实战使用FileBeats将日志发送到Logstash配置Logstash接收FileBeat收集的数据并打印Logstash输出数据到Elasticsearch利用Logstash过滤器解析日志Grok插件Grok语法用法 输出到Elasticsearch指定索引 前文:FileBeats详解 前文:logstash详解…...
网络编程:OSI协议,TCP/IP协议,IP地址,UDP编程
目录 国际网络通信协议标准: 1.OSI协议: 2.TCP/IP协议模型: 应用层 : 传输层: 网络层: IPV4协议 IP地址 IP地址的划分: 公有地址 私有地址 MA…...
QtExa001自动包装流水线的框架设计vs2019QT
QtExa001自动包装流水线的框架设计 工程代码: https://download.csdn.net/download/txwtech/89636815https://download.csdn.net/download/txwtech/89636815 主界面: 设置:进行参数配置,保存ini文件 调试:tcp/ip&…...
SpringBoot拦截器的使用介绍
SpringBoot拦截器的使用介绍 本篇文章主要讲的是 SpringBoot 拦截器的使用介绍。 1、定义拦截器 拦截器:所谓拦截器,就是能够在进行某个操作之前拦截请求,如果请求符合条件就允许在往下执行。 定义拦截器的几种方式。 1.1 实现HandleInt…...
Spring Boot应用中的资源分离与高效打包实践
在电商网站项目中,前端资源通常包括HTML、CSS、JavaScript、图片、字体等静态文件,以及Thymeleaf或Freemarker等模板引擎渲染的页面。将这些资源从Spring Boot主应用中分离出来,不仅有利于前后端团队的并行开发,还能提高应用的加载…...
分析 avformat_open_input 数据读取过程
------------------------------------------------------------ author: hjjdebug date: 2024年 08月 13日 星期二 17:31:43 CST descriptor: 分析 avformat_open_input 数据读取过程 ------------------------------------------------------------ avformat_open_input 中读…...
Apache HOP (Hop Orchestration Platform) VS Data Integration (通常被称为 Kettle)
Apache HOP (Hop Orchestration Platform) 和 Data Integration (通常被称为 Kettle) 都是强大的 ETL (Extract, Transform, Load) 工具, 它们都由 Hitachi Vantara 开发和支持。尽管它们有着相似的目标,即帮助用户进行数据集成任务,但它们在…...
如何判断一个dll/exe是32位还是64位
通过记事本判断(可判断C或者C#) 64位、将dll用记事本打开,可以看到一堆乱码,但是找到乱码行的第一个PE,如果后面是d?则为64位 32位、将dll用记事本打开,可以看到一堆乱码,但是找到乱码行的第…...
加速网页加载,提升用户体验:HTML、JS 和 Vue 项目优化全攻略
在信息爆炸的时代,网页加载速度成为了用户体验的重中之重。试想一下,如果一个页面加载超过 3 秒,你还有耐心等待吗? 为了留住用户,提升转化率,网页优化势在必行! 本文将从 HTML、JavaScript 和…...
LVS服务器基础环境配置
环境配置 1 基础服务关闭 setenforce 0 # 临时关闭selinuxvi /etc/sysconfig/selinux # 永久关闭selinuxsystemctl disable --now firewalld # 关闭防火墙systemctl disable --now NetworkManager # 关闭网络管理器2 centos7软件仓库的配置 mount /dev/cdrom /media以防万一&…...
【Python OpenCV】使用OpenCV实现两张图片拼接
问题引入: 如何使用Python OpenCV实现两张图片的水平拼接和垂直拼接 代码实现: import cv2 import numpy as npdef image_hstack(image_path_1, image_path_2):"""两张图片左右拼接"""img1 cv2.imread(image_path_1)img…...
springboot jar -jar centos后台运行的几种方式
在CentOS系统中,如果你想要在后台运行一个Spring Boot应用程序,你可以使用nohup命令或者使用screen会话。以下是两种常用的方法: 1. **使用nohup命令**: nohup命令可以使进程在你退出SSH会话后继续运行。它还会把标准输出和标…...
【GitLab】使用 Docker 安装 GitLab:配置 SSH 端口
使用 Docker 安装 GitLab 要求修改ssh端口 GitLab 使用 SSH 通过 SSH 与 Git 交互。默认情况下,GitLab 使用端口22。 要在使用 GitLab Docker 映像时使用其他端口,您可以执行以下操作之一: 更改服务器的 SSH 端口(推荐)。 更改 GitLab Shell SSH 端口。 更改服务器的 SSH …...
【pdf文件生成】如何将盖章的文件生成PDF文件
一、提出问题 在我们的工作中,有时候上级让下级将盖章的文件生成PDF文件通过内部平台发送到上级邮箱,那如何解决呢?是去找一个扫描仪,还是用手机拍图转。用Python基实就能实现。 二、分析问题 现在网上好多的软件都是收费的&am…...
铝壳电阻在电路中的作用和影响是什么?
铝壳电阻,顾名思义,就是用铝材料制成的电阻。在电路中,它主要起到限流、分压、负载等作用。下面详细介绍铝壳电阻在电路中的作用和影响。 1. 限流作用:铝壳电阻可以限制电流的大小,防止电流过大而损坏电路。当电路中的…...
# Python 判断入参日期是周几
在数据分析和软件开发中,经常需要判断某个特定日期是星期几。Python 提供了强大的日期时间处理功能,可以轻松实现这一功能。本篇文章将介绍如何使用 Python 的内置库来判断给定日期是星期几,并提供具体实例。 1. 使用 datetime 模块 Python…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...
[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
