【C# Programming】委托和lambda表达式、事件

目录

一、委托和lambda表达式

1.1 委托概述

1.2 委托类型的声明

1.3 委托的实例化

1.4 委托的内部机制 

1.5 Lambda 表达式

1.6 语句lambda

1.7 表达式lambda

1.8 Lambda表达式

1.9 通用的委托

1.10 委托没有结构相等性

1.11 Lambda表达式和匿名方法的内部机制

1.12 外部变量

1.13 外部变量的CIL实现

二、事件

2.1 多播委托

2.2 使用多播委托来编码Observer模式

2.3 委托操作符

2.4 错误处理

2.5 事件的作用

2.6 事件的声明

2.7 编码规范

2.8 事件的内部机制

2.9 自定义事件的实现


一、委托和lambda表达式

1.1 委托概述

        C/C++ 利用”函数指针”将方法的引用作为实参传给另一个方法。 C# 利用委托提供相同的功能。委托允许捕获对方法的引用,并像传递其他对象那样传递这个引用,像调用其他方法那样调用这个被捕获的方法。 例如:

public static void BubbleSort(int[] items, ComparisonHandler comparisonMethod)
{int i, j, temp;if(comparisonMethod == null)throw new ArgumentNullException("comparisonMethod");if(items == null)return;for(i = items.Length - 1; i >= 0; i--){for(j = 1; j <= i; j++){if(comparisonMethod(items[j - 1], items[j])){temp = items[j - 1];items[j - 1] = items[j];items[j] = temp;}}}
}

1.2 委托类型的声明

        为了声明委托,要使用delegate 关键字,后面跟着方法的签名。这个方法的签名是委托所引用的方法的签名。 例如:

public delegate bool ComparisonHandler(int first, int second);

        委托可以嵌套在类中。 假如委托声明出现在另一个类的内部,则委托类型就会成为嵌套类型。

class DelegateSample
{public delegate bool ComparisonHandler(int first, int second);
}

1.3 委托的实例化

        为了实例化委托,需要一个和委托类型自身签名匹配的方法。 例如:

public delegate bool ComparisonHandler(int first, int second);
class DelegateSample
{public static void BubbleSort(int[] items, ComparisonHandler comparisonMethod){//…}public static bool GreaterThan(int first, int second){return first > second;}static void Main(){int i;int[] items = new int[5];for(i = 0; i < items.Length; i++){Console.Write("Enter an integer: ");items[i] = int.Parse(Console.ReadLine());}BubbleSort(items, GreaterThan);for(i = 0; i < items.Length; i++)Console.WriteLine(items[i]);}
}

1.4 委托的内部机制 

        委托是特殊的类,.Net 中的委托类型总是派生自System.MulticastDelegate, 后者又从 System.Delegate 派生。

        C# 编译器不允许声明直接或间接从System.Delegate 或者System.MulicastDelegate 派生的类。

1.5 Lambda 表达式

        C#2.0 引入非常精简的语法创建委托,相关的特性被称为匿名方法。 C# 3.0 相关的特性称为Lambda 表达式。这两种语法统称 匿名函数。Lambda   表达式本身分为两种类型: 语句lambda和表达式 lambda。

1.6 语句lambda

        语句lambda 由形参列表,后面跟lambda 操作符=>, 然后跟一个代码块构成。 例如: 

public class DelegateSample
{//…..public static void Main(){int i;int[] items = new int[5];for(i = 0; i < items.Length; i++){Console.Write("Enter an integer: ");items[i] = int.Parse(Console.ReadLine());}BubbleSort(items, (int first, int second) => { return first < second;});for(i = 0; i < items.Length; i++){Console.WriteLine(items[i]);}}
}

        当编译器能从Lambda表达式转换成的委托推断出类型,所有Lambda都不需要显式声明参数类型。 在不能推断出类型时,C#要求显式指定Lambda类型。只要显式指定了一个Lambda参数类型,所有参数类型都必须被显式指定, 例如:

public static void ChapterMain()
{int i;int[] items = new int[5];for(i = 0; i < items.Length; i++){Console.Write("Enter an integer:");items[i] = int.Parse(Console.ReadLine());}DelegateSample.BubbleSort(items, (first, second) => { return first < second;});for(i = 0; i < items.Length; i++){Console.WriteLine(items[i]);}
}

        当只有单个参数,而且类型可以推断时,这种Lambda 表达式可省略围绕参数列表的圆括号。如果Lambda没有参数, 或者有不止一个参数,或者显式指定了类型的单个参数,那么就必须将参数列表放到圆括号中。  例如:

public class Program
{public static void ChapterMain(){IEnumerable<Process> processes = Process.GetProcesses().Where(process => { return process.WorkingSet64 > 1000000000; });}
}

        无参数的语句Lambda

public static void ChapterMain()
{Func<string> getUserInput = () =>{string input;do{input = Console.ReadLine();}while(input.Trim().Length == 0);return input;};
}

1.7 表达式lambda

        表达式lambda只有要返回的表达式,完全没有语句块。例如:

public static void Main()
{int i;int[] items = new int[5];for(i = 0; i < items.Length; i++){Console.Write("Enter an integer:");items[i] = int.Parse(Console.ReadLine());}DelegateSample.BubbleSort(items, (first, second) => first < second);for(i = 0; i < items.Length; i++){Console.WriteLine(items[i]);}
}

        和null字面量相似,匿名函数不与任何类型关联。它的类型由它将要转换成的类型决定。所以不能对一个匿名方法使用typeof()操作符。另外,只有将匿名方法转换成一个特定类型后才能调用GetType

1.8 Lambda表达式

1.9 通用的委托

        .Net 3.5 包含了一组通用的委托。System.Func 系列委托代表有返回值方法,而System.Action 系列委托代表返回void 的方法。Func 委托的最后一个参数总是委托的返回类型,其他参数依次对应于委托参数的类型。

//public delegate void Action();
//public delegate void Action<in T>(T arg);
//public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
//public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3);
//public delegate void Action<in T1, in T2, in T3, in T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
// ...
//public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16>(
//        T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, 
//         T13 arg13, T14 arg14, T15 arg15, T16 arg16);//public delegate TResult Func<out TResult>();
//public delegate TResult Func<in T, out TResult>(T arg);
//public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
//public delegate TResult Func<in T1, in T2, in T3, out TResult>(T1 arg1, T2 arg2, T3 arg3);
//public delegate TResult Func<in T1, in T2, in T3, in T4, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
// ...
//public delegate TResult Func<   in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16,
//       out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12,
//        T13 arg13, T14 arg14, T15 arg15, T16 arg16);             

        在许多情况下,.NET 3.5 添加的Func 委托能完全避免定义自己的委托类型。例如:

public static void BubbleSort(int[] items, Func<int, int, bool> comparisonMethod)

1.10 委托没有结构相等性

         .NET 委托类型不具备结构相等性。也就是说,不能将某个委托类型的对象转换成不相关的委托类型,即使这两个委托类型的形参和返回类型完全一致。 然而,通过C# 4.0 添加的对可变性的支持,可以在某些引用类型之间进行引用转换。

public static void ChapterMain()
{// ContravarianceAction<object> broadAction =(object data) =>{Console.WriteLine(data);};Action<string> narrowAction = broadAction;// CovarianceFunc<string> narrowFunction =() => Console.ReadLine();Func<object> broadFunction = narrowFunction;// Contravariance and covariance combinedFunc<object, string> func1 =(object data) => data.ToString();Func<string, object> func2 = func1;
}

1.11 Lambda表达式和匿名方法的内部机制

        当编译器遇到匿名方法时, 会把它转换为特殊的隐藏的类,字段和方法。 例如:

public static void Main()
{int i;int[] items = new int[5];for(i = 0; i < items.Length; i++){Console.Write("Enter an integer:");items[i] = int.Parse(Console.ReadLine());}BubbleSort(items, DelegateSample.__AnonymousMethod_00000000);for(i = 0; i < items.Length; i++){Console.WriteLine(items[i]);}
}
private static bool __AnonymousMethod_00000000(int first, int second)
{return first < second;
}

1.12 外部变量

        在Lambda表达式外部声明的局部变量称为表达式的外部变量。当Lambda主体使用一个外部变量时, 就说该变量被这个lambda捕获

public static void ChapterMain()
{int i;int[] items = new int[5];int comparisonCount = 0;for(i = 0; i < items.Length; i++){Console.Write("Enter an integer:");items[i] = int.Parse(Console.ReadLine());}DelegateSample.BubbleSort(items, (int first, int second) => {comparisonCount++; return first < second; });for(i = 0; i < items.Length; i++)Console.WriteLine(items[i]);Console.WriteLine("Items were compared {0} times.", comparisonCount);
}

        如果Lambda表达式捕获了外部变量,根据该表达式创建委托可能具有比局部变量更长的生存期。在此情况下,被捕获的变量的生存期变长了。

1.13 外部变量的CIL实现

        在Lambda表达式外部声明的局部变量称为表达式的外部变量。当Lambda主体使用一个外部变量时, 就说该变量被这个lambda捕获

private sealed class __LocalsDisplayClass_00000001
{public int comparisonCount;public bool __AnonymousMethod_00000000(int first, int second){comparisonCount++;return first < second;}
}
public static void Main()
{int i;__LocalsDisplayClass_00000001 locals = new __LocalsDisplayClass_00000001();locals.comparisonCount = 0;int[] items = new int[5];for(i = 0; i < items.Length; i++){Console.Write("Enter an integer:");items[i] = int.Parse(Console.ReadLine());}DelegateSample.BubbleSort(items, locals.__AnonymousMethod_00000000);for(i = 0; i < items.Length; i++)Console.WriteLine(items[i]);Console.WriteLine("Items were compared {0} times.", locals.comparisonCount);
}

二、事件

2.1 多播委托

        委托本身是发布-订阅模式的基本单位

        一个委托值可以引用一系列方法的, 这些方法将顺序调用。这样的委托称为多播委托。利用多播委托,单一事件的通知可以发布给多个订阅者

2.2 使用多播委托来编码Observer模式

        定义订阅者方法

class Cooler
{public Cooler(float temperature) {Temperature = temperature;}public float Temperature { get; set; }public void OnTemperatureChanged(float newTemperature){if(newTemperature > Temperature)System.Console.WriteLine("Cooler: On");elseSystem.Console.WriteLine("Cooler: Off");}
}
class Heater
{public Heater(float temperature) {Temperature = temperature;}public float Temperature { get; set; }public void OnTemperatureChanged(float newTemperature){if(newTemperature < Temperature)System.Console.WriteLine("Heater: On");elseSystem.Console.WriteLine("Heater: Off");}
}

        定义发布者

public class Thermostat
{public Action<float> OnTemperatureChange { get; set; }public float CurrentTemperature{get { return _CurrentTemperature; }set{if(value != CurrentTemperature){_CurrentTemperature = value;}}}private float _CurrentTemperature;
}

        连接发布者和订阅者

public static void Main()     
{        Thermostat thermostat = new Thermostat();            Heater heater = new Heater(60);Cooler cooler = new Cooler(80);            string temperature;            // Using C# 2.0 or later syntax.          thermostat.OnTemperatureChange +=                heater.OnTemperatureChanged;       thermostat.OnTemperatureChange +=                cooler.OnTemperatureChanged;       Console.Write("Enter temperature: ");           temperature = Console.ReadLine();          thermostat.CurrentTemperature = int.Parse(temperature);     
}

        调用委托

public class Thermostat
{// Define the event publisherpublic Action<float> OnTemperatureChange { get; set; }public float CurrentTemperature{get { return _CurrentTemperature; }set{if(value != CurrentTemperature){_CurrentTemperature = value;// INCOMPLETE:  Check for null needed Call subscribersOnTemperatureChange(value);}}}private float _CurrentTemperature;
}

        检查null值

public static void ChapterMain()
{Thermostat thermostat = new Thermostat();Heater heater = new Heater(60);Cooler cooler = new Cooler(80);Action<float> delegate1;Action<float> delegate2;Action<float> delegate3;// use Constructor syntax for C# 1.0.delegate1 = heater.OnTemperatureChanged;delegate2 = cooler.OnTemperatureChanged;Console.WriteLine("Invoke both delegates:");delegate3 = delegate1;delegate3 += delegate2;delegate3(90);Console.WriteLine("Invoke only delegate2");delegate3 -= delegate1;delegate3(30);
}

2.3 委托操作符

        为了合并多个订阅者,要使用+= 操作符。这个操作符会获取第一个委托,并将第二个委托添加到委托链中。 要从委托链中删除委托,则要使用-=操作符

public static void ChapterMain()
{Thermostat thermostat = new Thermostat();Heater heater = new Heater(60);Cooler cooler = new Cooler(80);Action<float> delegate1;Action<float> delegate2;Action<float> delegate3;// use Constructor syntax for C# 1.0.delegate1 = heater.OnTemperatureChanged;delegate2 = cooler.OnTemperatureChanged;Console.WriteLine("Invoke both delegates:");delegate3 = delegate1;delegate3 += delegate2;delegate3(90);Console.WriteLine("Invoke only delegate2");delegate3 -= delegate1;delegate3(30);
}

        我们还可以使用+ 和- 合并委托

public static void ChapterMain()
{Thermostat thermostat = new Thermostat();Heater heater = new Heater(60);Cooler cooler = new Cooler(80);Action<float> delegate1, delegate2, delegate3;delegate1 = heater.OnTemperatureChanged;delegate2 = cooler.OnTemperatureChanged;Console.WriteLine("Combine delegates using + operator:");delegate3 = delegate1 + delegate2;delegate3(60);Console.WriteLine("Uncombine delegates using - operator:");delegate3 = delegate3 - delegate2;delegate3(60);
}

        无论+ - 还是+= 、-=,在内部都是使用静态方法system.Delegate.combine() 和System.Delegate.Remove()来实现的。Combine 会连接两个参数,将两个委托的调用列表按顺序连接起来,Remove 则将第二个参数指定的委托删除

        顺序调用

2.4 错误处理

        假如一个订阅者发生了异常,链中后续的订阅者就收不到通知

public static void Main()
{Thermostat thermostat = new Thermostat();Heater heater = new Heater(60);Cooler cooler = new Cooler(80);string temperature;thermostat.OnTemperatureChange +=heater.OnTemperatureChanged;thermostat.OnTemperatureChange +=(newTemperature) =>{throw new InvalidOperationException();};thermostat.OnTemperatureChange +=cooler.OnTemperatureChanged;Console.Write("Enter temperature: ");temperature = Console.ReadLine();thermostat.CurrentTemperature = int.Parse(temperature);
}

        为了避免该问题,必须手动遍历委托链并单独调用委托

public class Thermostat {public Action<float> OnTemperatureChange;public float CurrentTemperature{get { return _CurrentTemperature; }set{if(value != CurrentTemperature) {_CurrentTemperature = value;Action<float> onTemperatureChange = OnTemperatureChange;if (onTemperatureChange != null) {List<Exception> exceptionCollection =   new List<Exception>();foreach(Action<float> handler in onTemperatureChange.GetInvocationList()){try  {handler(value);}catch(Exception exception)   {exceptionCollection.Add(exception);}}if(exceptionCollection.Count > 0) throw new AggregateException(  "There were exceptions thrown by " +  "OnTemperatureChange Event subscribers.", exceptionCollection);}}}}private float _CurrentTemperature;
}

2.5 事件的作用

        封装订阅: 事件仅对包容类内部对象提供对赋值操作符的支持。

public static void Main()
{Thermostat thermostat = new Thermostat();Heater heater = new Heater(60);Cooler cooler = new Cooler(80);string temperature;thermostat.OnTemperatureChange = heater.OnTemperatureChanged;// Bug: Assignment operator overrides // previous assignment.thermostat.OnTemperatureChange =    cooler.OnTemperatureChanged;Console.Write("Enter temperature: ");temperature = Console.ReadLine();thermostat.CurrentTemperature = int.Parse(temperature);
}

        封装发布: 事件确保只有包容类才能触发异常

public static void ChapterMain()
{//……..thermostat.OnTemperatureChange +=  heater.OnTemperatureChanged;thermostat.OnTemperatureChange +=cooler.OnTemperatureChanged;// Bug: Should not be allowedthermostat.OnTemperatureChange(42);
}

2.6 事件的声明

        C# 使用事件解决了委托的两大问题。Event 定义了一个新的成员类型,例如:

public class Thermostat 
{public class TemperatureArgs : System.EventArgs {public TemperatureArgs(float newTemperature){NewTemperature = newTemperature;}public float NewTemperature { get; set; }}// Define the event publisherpublic event EventHandler<TemperatureArgs> OnTemperatureChange = delegate { };public float CurrentTemperature{get { return _CurrentTemperature; }set { _CurrentTemperature = value; }}private float _CurrentTemperature;
}

         添加关键字event后,会禁止为一个public 委托字段使用赋值操作符,只有包容类才能调用向所有委托发出的通知委托; delegate{}表示一个空委托,代表由零个侦听者构成的集合。通过赋值空委托,可以引发事件而不必检查是否有侦听者

2.7 编码规范

        为了获得所需功能,需要将原始委托变量声明为字段,然后添加event关键字。为了遵循C#编码规范,需要将原始委托替换成新的委托类型 EventHandle,例如:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)where TEventArgs : EventArgs;
  • 第一参数sender是object 类型, 它包含对调用委托的那个对象的一个引用(静态事件为null)  
  • 第二参数是System.EventArgs类型的,或者从System.EventArgs派生,但包含了事件的附加数据。

        触发事件通知

public float CurrentTemperature
{get { return _CurrentTemperature; }set {if(value != CurrentTemperature){_CurrentTemperature = value;// If there are any subscribers, notify them of changes in temperature by invoking said subcribersOnTemperatureChange?.Invoke( this, new TemperatureArgs(value));}}
}

规范 :

  • 要在调用委托前检查它的值不为null  
  • 不要为非静态事件的sender传递null 值  
  • 要为静态事件的sender传递null值
  • 不要为eventArgs传递null值 要为事件使用EventHandler<TEventArgs> 委托类型
  • 要为TEventArgs 使用System.EventArgs类型或者它的派生类型  
  • 考虑使用System.EventArgs的子类作为事件的实参类型,除非完全确定事件永远不需要携带任何数据

2.8 事件的内部机制

        事件限制外部类只能通过+=向发布者添加订阅方法,并用-=取消订阅,除此之外任何事情不允许做。此外,它还禁止除包容类之外的任何类调用事件。为此,编译器会获取带有event修饰符的public 委托变了,并将委托声明为private,此外还添加两个方法和特殊的事件块。

public class Thermostat {// ...// Declaring the delegate field to save the list of subscribers.private EventHandler<TemperatureArgs> _OnTemperatureChange;public void add_OnTemperatureChange(EventHandler<TemperatureArgs> handler) {System.Delegate.Combine(_OnTemperatureChange, handler);}public void remove_OnTemperatureChange(  EventHandler<TemperatureArgs> handler) {System.Delegate.Remove(_OnTemperatureChange, handler);}//public event EventHandler<TemperatureArgs> OnTemperatureChange//{//    add//    {//        add_OnTemperatureChange(value);//    }//    remove//    {//        remove_OnTemperatureChange(value);//    }//}public class TemperatureArgs : System.EventArgs {public TemperatureArgs(float newTemperature) {}}
}

2.9 自定义事件的实现

        编译器为+=和-=生成的代码是可以自定义的,例如

public class Thermostat {public class TemperatureArgs : System.EventArgs{//….}// Define the delegate data typepublic delegate void TemperatureChangeHandler(object sender, TemperatureArgs newTemperature);// Define the event publisherpublic event TemperatureChangeHandler OnTemperatureChange{add{_OnTemperatureChange = (TemperatureChangeHandler)System.Delegate.Combine(value, _OnTemperatureChange);}remove{_OnTemperatureChange = (TemperatureChangeHandler)System.Delegate.Remove(_OnTemperatureChange, value);}}protected TemperatureChangeHandler _OnTemperatureChange;public float CurrentTemperature{//......}
}

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

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

相关文章

零代码编程:用ChatGPT进行批量重命名时需要注意特殊字符

用ChatGPT进行批量重命名时&#xff0c;一部分成功了&#xff0c;但有一部分文件标题没有变化。 问下ChatGPT怎么回事&#xff1a; 回复&#xff1a;在Windows中&#xff0c;文件名不能包含以下字符&#xff1a;\ / : * ? " < > |&#xff0c;使用Python的 str.rep…

RISC-V IDE MRS无感远程协助模块详解

RISC-V IDE MRS无感远程协助模块详解 一、说明 1.1 概述 针对RISC-V/ARM等内核MCU的嵌入式集成开发环境MRS(MounRiver Studio)从V1.90版本开始内置无感远程协助模块&#xff08;Sensorless Remote Assistant Module&#xff0c;以下简称SRA模块&#xff09;。SRA模块是一款支…

【Java】LinkedList 集合

LinkedList集合特点 LinkedList 底层基于双向链表实现增删 效率非常高&#xff0c;查询效率非常低。 LinkedList源码解读分析 LinkedList 是双向链表实现的 ListLinkedList 是非线程安全的&#xff08;线程是不安全的&#xff09;LinkedList 元素允许为null,允许重复元素Linked…

跨境电商的新引擎:崛起的网红经济

随着全球数字化时代的崛起&#xff0c;跨境电商成为了国际贸易的新引擎&#xff0c;而在这个巨大的变革浪潮中&#xff0c;网红经济正在崭露头角&#xff0c;成为这一引擎的有力推动者。在这篇文章中&#xff0c;我们将深入探讨网红经济如何催生跨境电商的新动力&#xff0c;以…

蓝桥杯每日一题2023.10.31

题目描述 全球变暖 - 蓝桥云课 (lanqiao.cn) 题目分析 果然有关连通块类的问题使用dfs都较为好写~~ 我们可以通过判断连通块的代码来加上部分条件算出被完全淹没的岛屿个数 在岛屿中如果有为"#"的a[i][j]上下左右全部是"#"则说明此岛屿一定不会被完全…

IDEA在service面板中不显示微服务的项目

在.idea文件夹下的workspace文件中的project标签内添加如下代码段&#xff0c;&#xff0c;重启idea即可看到所有服务出现在了service面板中 <component name"RunDashboard"><option name"configurationTypes"><set><option value&q…

Debug技巧-不启用前端访问后端

在日常开发中&#xff0c;我们经常会遇到各种问题需要调试&#xff0c;前后端都启动需要耗费一定的时间和内存&#xff0c;方便起见&#xff0c;可以直接用抓包数据访问后端&#xff0c;这里我们需要用到Postman或者ApiFox 抓包数据 在系统前台触发后端请求&#xff0c;在控制…

pycharm怎么运行python代码

创建项目 在PyCharm中&#xff0c;你可以创建一个项目来组织和管理你的Python代码。项目是一个存放代码文件的文件夹&#xff0c;它可以包含多个模块和包。 启动PyCharm后&#xff0c;选择“Create New Project”来创建一个新项目。 在弹出的对话框中&#xff0c;选择项目的位…

zip2john 爆破zip工具

1.使用zip2john工具输出zip文件hash Zip2john 1.zip > 1.txt 2.使用john工具进行zip文件爆破 John 1.txt 如图所示&#xff1a; 该zip密码为19950101

创建javaEE项目(无maven),JSP(九大内置对象)、Servlet(生命周期)了解

一、Servlet和jsp 0.创建web项目(无maven)&#xff1a; 1.创建一个普通的java项目 2.项目根目录右键&#xff0c;添加模板 3.配置tomcat服务器 4.配置项目tomcat依赖 1.Servlet(Server Applet)服务端小程序 用户通过浏览器发送一个请求&#xff0c;服务器tomcat接收到后&…

一分钟教你批量删除文件名中特定文字

怎么批量删除文件名中特定文字&#xff1f;在这个信息化的时代&#xff0c;我们每天会通过电脑处理大量的电子文件。无论是个人还是企业用户&#xff0c;在文件管理过程中&#xff0c;常常会遇到文件名混乱、重复或包含特定文字等问题。这些问题对于文件的检索和整理造成了很大…

模块化机柜PDU为数据中心机房末端配电提供可靠解决方案

数据中心是国家确定的“新基建”七大领域之一&#xff0c;数据中心在国民经济和社会发展中所起的作用越来越重要&#xff0c;数据中心已经成为了各行各业的关键基础设施&#xff0c;数据中心供配电系统相当于一个人的“心脏和血管”&#xff0c;负责把能量输送到系统的每一台设…