不要把异常当做业务逻辑,这性能可能你无法承受

一:背景

1. 讲故事

在项目中摸爬滚打几年,应该或多或少的见过有人把异常当做业务逻辑处理的情况(┬_┬),比如说判断一个数字是否为整数,就想当然的用try catch包起来,再进行 int.Parse,如果抛异常就说明不是整数,简单粗暴,也不需要写正则或者其他逻辑,再比如一个字符串强制转化为Enum,直接用Enum.Parse,可能是因为对异常的开销不是特别了解,这种不好的使用习惯也许被官方发现了,后续给我们补了很多的Try前缀的方法,比如:int.TryParse , Enum.TryParsedict.TryGetValue ,用代码展示如下:

            //原始写法var num = int.Parse("1");//使用try方式var result = 0;var b = int.TryParse("1", out result);

用Try系列方法没毛病,但这写法让人吐槽,还要单独定义result变量,没撤,官方还得靠我们这些开发者给他们发扬光大????????????,终于在C# 7.0 中新增了一个 out variables 语法糖。

//try out 变量模式var c = int.TryParse("1", out int result2);

这种 out 变量 模式就????????了,一个方法获取两个值,还没有抛异常的风险。

二:为什么要用tryxxx方法

有了tryxxx方法之后,你就应该明白微软已经在提醒我们开发人员不要滥用异常,尤其在可预知可预见的场景下,毕竟他们知道异常的开销真的是太大了,不知者不怪哈。

1. 肉眼看得见的低性能

为了让大家肉眼能看见,我们就用异常方法和tryxxx方法做一个性能比较,迭代50w次,看看各自的性能如何?

for (int i = 0; i < 3; i++){var watch = Stopwatch.StartNew();for (int k = 0; k < 50000; k++){try{var num = int.Parse("xxx");}catch (Exception ex) { }}watch.Stop();Console.WriteLine($"i={i + 1},耗费:{watch.ElapsedMilliseconds}");}Console.WriteLine("---------------------------------------------");for (int i = 0; i < 3; i++){var watch = Stopwatch.StartNew();for (int k = 0; k < 50000; k++){var num = int.TryParse("xxx", out int reuslt);}watch.Stop();Console.WriteLine($"i={i + 1},耗费:{watch.ElapsedMilliseconds}");}Console.ReadLine();

看结果还挺吓人的,相差480倍, 好熟悉的一个数字。。。 南朝四百八十寺,多少楼台烟雨中 ????????????

三:异常的超强开销

为什么异常有那么大的开销?只有知己知彼才能心中有数,看过我多线程视频的朋友应该知道,线程的创建和销毁代价都是非常大的,其中有一项就是需要代码从用户态切换到了内核态,毕竟线程是操作系统层面的事情,和你CLR无关,CLR只是做了一层系统包装而已,其实很多人都想不到,我们用的 try catch finally 底层也是封装了操作系统层面的(Windows 结构化异常处理),也叫做SEH,什么意思?就是当你throw之后,代码需要从用户态切换到内核态,这个开销是不会小的,还有一个开销来自于Exception中的StackTrace,这里面的值需要从当前异常的线程栈中去抓取调用堆栈,栈越深,开销就越大。

1. 从用户态到内核态

大家肯定会说,甭那么玄乎,凡事都要讲个证据, Do more,Talk less, 这里我准备分两种情况讲解。

<1> 有catch情况

准备在catch的时候阻塞住,然后抓它的dump文件。

public static void Main(string[] args){try{var num = int.Parse("xxx");}catch (Exception ex){Console.WriteLine(ex.Message);Console.ReadLine();}}

使用 !dumpstack 把当前 0号线程 的所有托管和非托管堆栈全部打出来,简化后如下:


0:000> ~0s
ntdll!NtReadFile+0x14:
00007fff`f805aa64 c3              ret
0:000> !dumpstack
OS Thread Id: 0x2bf0 (0)
Current frame: ntdll!NtReadFile+0x14
Caller, Callee
(MethodDesc 00007fffde3a40b8 +0x18 System.Console.ReadLine())
(MethodDesc 00007fff810d59f8 +0xa5 ConsoleApp4.Program.Main(System.String[])), calling (MethodDesc 00007fffde3a40b8 +0 System.Console.ReadLine())
00000044433fc700 00007fffe07a29e0 clr!ExceptionTracker::CallCatchHandler+0x9c, calling clr!ExceptionTracker::CallHandler
clr!ClrUnwindEx+0x40, calling ntdll!RtlUnwindEx
ntdll!RtlRaiseException+0x4e, calling ntdll!RtlpCaptureContext
clr!IL_Throw+0x114, calling clr!RaiseTheExceptionInternalOnly
(MethodDesc 00007fffde4f95c0 System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)), calling mscorlib_ni+0x53976a
(MethodDesc 00007fffde3b5330 +0xae System.Number.ParseInt32(System.String, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)), calling (MethodDesc 00007fffde4f95c0 +0 System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean))
(MethodDesc 00007fffde1ebfa8 +0x2eb System.Globalization.NumberFormatInfo..ctor(System.Globalization.CultureData)), calling (MethodDesc 00007fffde1eba68 +0 System.Globalization.CultureData.GetNFIValues(System.Globalization.NumberFormatInfo))
(MethodDesc 00007fff810d59f8 +0x49 ConsoleApp4.Program.Main(System.String[])), calling (MethodDesc 00007fffde3b1708 +0 System.Int32.Parse(System.String))

因为是堆栈,所以执行流就要从后往前看,你会发现流程大概是这个样子 int.Parse -> CLR -> ntdll -> CLR -> Console.ReadLine ,很显然 ntdll.dll 是操作系统层级的一个核心文件,这就从用户态切入到了内核态,如果不是很明白,我画一张简图吧。。。

<2>. 无catch处理

大家肯定很好奇,如果无catch会是怎么样,大家也可以用windbg去挖一下。

public static void Main(string[] args){var num = int.Parse("xxx");}0:000> !dumpstack
OS Thread Id: 0xd68 (0)
Current frame: ntdll!NtTerminateProcess+0x14
Caller, Callee
mscoreei!RuntimeDesc::ShutdownAllActiveRuntimes+0x285, calling KERNEL32!ExitProcessImplementation
mscoreei!CLRRuntimeHostInternalImpl::ShutdownAllRuntimesThenExit+0x14, calling mscoreei!RuntimeDesc::ShutdownAllActiveRuntimes
clr!EEPolicy::ExitProcessViaShim+0x9c
clr!SafeExitProcess+0x9d, calling clr!EEPolicy::ExitProcessViaShim
ntdll!KiUserExceptionDispatch+0x53, calling ntdll!NtRaiseException
clr!RaiseTheExceptionInternalOnly+0x188426, calling clr!EEPolicy::HandleFatalError
clr!IL_Throw+0x45, calling clr!LazyMachStateCaptureState
(MethodDesc 00007fffde4f95c0 System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)), calling mscorlib_ni+0x53976a
(MethodDesc 00007fffde3b5330 +0xae System.Number.ParseInt32(System.String, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)), calling (MethodDesc 00007fffde4f95c0 +0 System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean))
(MethodDesc 00007fffde1ebfa8 +0x2eb System.Globalization.NumberFormatInfo..ctor(System.Globalization.CultureData)), calling (MethodDesc 00007fffde1eba68 +0 System.Globalization.CultureData.GetNFIValues(System.Globalization.NumberFormatInfo))
(MethodDesc 00007fff810e59f8 +0x37 ConsoleApp4.Program.Main(System.String[])), calling (MethodDesc 00007fffde3b1708 +0 System.Int32.Parse(System.String))

可以看到进程的退出逻辑给了托管程序入口 mscoreei.dll 而再也没有进入Main函数了, 为此我也补一张图给大家看看

2. 抓取线程调用栈

当大家慌慌张张的看到异常的时候,第一眼会去看异常信息是什么?第二眼会去看异常出在了哪一行代码,这就是线程的调用栈,这个信息非常重要,可以快捷的帮助我们找到问题解决问题,放在Exception的StackTrace中,先上一段代码。

public static void Main(string[] args){Run();Console.ReadLine();}public static void Run(){var ex = new FormatException("你的格式错误啦!!!");throw ex;}

<1> StackTrace何时塞入的

到目前为止还没看到哪本书说到StackTrace是何时被塞入的?由于水平有限,我也试着探测一下下。

从代码中可以看到不是在new的时候塞入的,那会是哪里呢?

<2> 从CLR中寻找答案

既然不在用户代码,那就到CLR中去看看,在windbg中用 dumpstack 去查看非托管堆栈。


0:000> !dumpstack
OS Thread Id: 0x4090 (0)
Current frame: ntdll!NtTerminateProcess+0x14
Caller, Callee
clr!EETypeHashTable::FindItem+0x532, calling clr!NgenHashTable<EEClassHashTable,EEClassHashEntry,4>::PersistedBucketList::GetBucket
clr!JIT_StrCns+0xd0, calling clr!HelperMethodFrameRestoreState
(MethodDesc 00007fff810f5a08 +0x70 ConsoleApp4.Program.Run()), calling clr!IL_Throw
clr!IL_Throw+0x45, calling clr!LazyMachStateCaptureState
(MethodDesc 00007fff810f5a08 +0x70 ConsoleApp4.Program.Run()), calling clr!IL_Throw
(MethodDesc 00007fff810f59f8 +0x28 ConsoleApp4.Program.Main(System.String[])), calling 00007fff81200488 (stub for ConsoleApp4.Program.Run())

从简化后的流程看,怀疑是由 clr!HelperMethodFrameRestoreState 处理的,为什么这么说呢?因为我们定义的 FormatException ex 会传给CLR的,不信可以用 kb 看一看。


0:000> kb# RetAddr           : Args to Child                                                           : Call Site
00 00007fff`e07a3181 : 00000000`e0434352 0000006d`4a7fe938 0000017b`30ad2d48 0000017b`2f081690 : KERNELBASE!RaiseException+0x68
01 00007fff`e07a45f4 : ffffffff`fffffffe 0000017b`2ef02542 00000000`0000000a 0000017b`2f040910 : clr!RaiseTheExceptionInternalOnly+0x31f
02 00007fff`811d0950 : 00000000`70000001 00007fff`810c4140 0000006d`4a7fedb8 0000006d`4a7fec78 : clr!IL_Throw+0x114
03 00007fff`811d08b8 : 0000017b`30ad2d30 00007fff`810c4140 00000000`00000000 00007fff`00000000 : 0x00007fff`811d0950
04 00007fff`e0736c93 : 0000017b`30ad2d30 00007fff`810c4140 00000000`00000000 00007fff`00000000 : 0x00007fff`811d08b8
05 00007fff`e0736b79 : 00000000`00000000 00007fff`e0737aae 0000006d`4a7fefb8 00000000`00000000 : clr!CallDescrWorkerInternal+0x83
06 00007fff`e0737410 : 0000006d`4a7fefb8 0000006d`4a7ff048 0000006d`4a7feeb8 00000000`00000001 : clr!CallDescrWorkerWithHandler+0x4e
07 00007fff`e08dcaf2 : 0000006d`4a7fee00 00000000`00000001 00000000`00000001 0000017b`2efcecf0 : clr!MethodDescCallSite::CallTargetWorker+0x102
08 00007fff`e08dd4b3 : 00000000`00000001 00000000`00000000 0000017b`30ad2d30 0000017b`30ad2d30 : clr!RunMain+0x25f
09 00007fff`e08dd367 : 0000017b`2f040910 0000006d`4a7ff420 0000017b`2f040910 0000017b`2f082770 : clr!Assembly::ExecuteMainMethod+0xb7
0a 00007fff`e08dccb3 : 00000000`00000000 0000017b`2ef00000 00000000`00000000 00000000`00000000 : clr!SystemDomain::ExecuteMainMethod+0x643
0b 00007fff`e08dcc31 : 0000017b`2ef00000 00007fff`e08de090 00000000`00000000 00000000`00000000 : clr!ExecuteEXE+0x3f
0c 00007fff`e08de0a4 : ffffffff`ffffffff 00007fff`e08de090 00000000`00000000 00000000`00000000 : clr!_CorExeMainInternal+0xb2
0d 00007fff`e1208a61 : 00000000`00000000 00007fff`00000091 00000000`00000000 0000006d`4a7ff9f8 : clr!CorExeMain+0x14
0e 00007fff`e133a4cc : 00000000`00000000 00007fff`e08de090 00000000`00000000 00000000`00000000 : mscoreei!CorExeMain+0x112
0f 00007fff`f5cc4034 : 00007fff`e1200000 00000000`00000000 00000000`00000000 00000000`00000000 : MSCOREE!CorExeMain_Exported+0x6c
10 00007fff`f8033691 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
11 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21

其中第一行的 00 00007fffe07a3181 : 00000000e0434352 0000006d4a7fe938 0000017b30ad2d48 0000017b2f081690 : KERNELBASE!RaiseException+0x68中的第三个参数地址0000017b30ad2d48` 就是我们的异常类,打印出来看一下。


0:000> !do 0000017b30ad2d48
Name:        System.FormatException
MethodTable: 00007fffde285c38
EEClass:     00007fffde3930e0
Size:        160(0xa0) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:MT    Field   Offset                 Type VT     Attr            Value Name
00007fffde2059c0  40002a2        8        System.String  0 instance 0000017b30ad4c80 _className
00007fffde282a50  40002a3       10 ...ection.MethodBase  0 instance 0000000000000000 _exceptionMethod
00007fffde2059c0  40002a4       18        System.String  0 instance 0000000000000000 _exceptionMethodString
00007fffde2059c0  40002a5       20        System.String  0 instance 0000017b30ad2de8 _message
00007fffde2883d8  40002a6       28 ...tions.IDictionary  0 instance 0000000000000000 _data
00007fffde205b70  40002a7       30     System.Exception  0 instance 0000000000000000 _innerException
00007fffde2059c0  40002a8       38        System.String  0 instance 0000000000000000 _helpURL
00007fffde205dd8  40002a9       40        System.Object  0 instance 0000017b30ad2e98 _stackTrace
00007fffde205dd8  40002aa       48        System.Object  0 instance 0000017b30ad2f28 _watsonBuckets
00007fffde2059c0  40002ab       50        System.String  0 instance 0000000000000000 _stackTraceString
00007fffde2059c0  40002ac       58        System.String  0 instance 0000000000000000 _remoteStackTraceString
00007fffde2085a0  40002ad       88         System.Int32  1 instance                0 _remoteStackIndex
00007fffde205dd8  40002ae       60        System.Object  0 instance 0000000000000000 _dynamicMethods
00007fffde2085a0  40002af       8c         System.Int32  1 instance      -2146233033 _HResult
00007fffde2059c0  40002b0       68        System.String  0 instance 0000000000000000 _source
00007fffde2831f8  40002b1       78        System.IntPtr  1 instance                0 _xptrs
00007fffde2085a0  40002b2       90         System.Int32  1 instance       -532462766 _xcode
00007fffde21e720  40002b3       80       System.UIntPtr  1 instance                0 _ipForWatsonBuckets
00007fffde1f5080  40002b4       70 ...ializationManager  0 instance 0000017b30ad2e18 _safeSerializationManager
00007fffde205dd8  40002a1      100        System.Object  0   shared           static s_EDILock>> Domain:Value  0000017b2efe0af0:NotInit  <<0:000> !do 0000017b30ad2e98
Name:        System.SByte[]
MethodTable: 00007fffde20dde8
EEClass:     00007fffde390920
Size:        120(0x78) bytes
Array:       Rank 1, Number of elements 96, Type SByte (Print Array)Content:     .........../{...P.......@..Jm....Z.........................Jm....Y..............................
Fields:
None

此时 _stackTrace 已经有值了,毕竟Console上已经打印出来了。

最后补充一下大家也可以通过 !threads 去找异常的线程,如下图的中 System.FormatException 0000017b30ad2d48,然后通过 !printexception 去打印这个地址 0000017b30ad2d48 上异常对象。


0:000> !threads
ThreadCount:      2
UnstartedThread:  0
BackgroundThread: 1
PendingThread:    0
DeadThread:       0
Hosted Runtime:   noLock  ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception0    1  80c 0000016816f508f0    2a020 Preemptive  0000016818CCE3B8:0000016818CCFFD0 0000016816ef0b10 0     MTA System.FormatException 0000017b30ad2d486    2 12d8 0000016816f7b0e0    2b220 Preemptive  0000000000000000:0000000000000000 0000016816ef0b10 0     MTA (Finalizer)0:000> !printexception 0000017b30ad2d48
Exception object: 0000017b30ad2d48
Exception type:   System.FormatException
Message:          你的格式错误啦!!!
InnerException:   <none>
StackTrace (generated):SP               IP               Function0000001F8F7FEE90 00007FFF811E0951 ConsoleApp4!ConsoleApp4.Program.Run()+0x710000001F8F7FEEE0 00007FFF811E08B9 ConsoleApp4!ConsoleApp4.Program.Main(System.String[])+0x29StackTraceString: <none>
HResult: 80131537

三:总结

不要把异常当做业务逻辑处理,这开销有可能你承受不起,把那些真正不可期的情况留给异常吧,如: TimeoutException。。。

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

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

相关文章

[设计模式]开闭原则

开闭原则: 对扩展开放&#xff0c;对修改关闭。 增加功能是提过增加代码来实现的&#xff0c;而不是去修改源代码。 代码如下: #include <iostream> #include <string> using namespace std;class Caculaor { public:Caculaor(int a,int b,string c):a(a),b(b…

#10010 「一本通 1.1 练习 6」糖果传递 (数学+贪心)

题目描述 原题来自&#xff1a;HAOI 2008 有 n个小朋友坐成一圈&#xff0c;每人有 ai 颗糖果。每人只能给左右两人传递糖果。每人每次传递一颗糖果的代价为 1 。求使所有人获得均等糖果的最小代价。 输入格式 第一行有一个整数 &#xff0c;n表示小朋友个数&#xff1b; …

ASP.NET Core在Docker下面生成简易验证码

背景 验证码这个功能是十分常见的&#xff0c;各大系统的登录页面都会有。今天介绍一下最为普通的验证码。无论最终给到前端的是图片格式的验证码还是base64格式的验证码&#xff0c;其实都离不开这样的一步操作&#xff0c;都要先在后台生成一个图片。就个人经验来说&#xff…

[设计模式]迪米特法则

迪米特法则 又叫最少知识法则 类中的成员属性和成员方法&#xff0c;如果不需要对外暴露&#xff0c;就不要设成public。 代码如下: #include <iostream> #include <string> using namespace std;class AbstractBuilding { public:virtual void sale() 0; };cl…

#10017 「一本通 1.2 练习 4」传送带+三分套三分

题目描述 原题来自&#xff1a;SCOI 2010 在一个 2 维平面上有两条传送带&#xff0c;每一条传送带可以看成是一条线段。两条传送带分别为线段 AB和线段CD 。lxhgww 在 AB上的移动速度为 P &#xff0c;在 CD上的移动速度为 Q &#xff0c;在平面上的移动速度R 。 现在 l…

程序员过关斩将--从用户输入手机验证码开始

点击“蓝字”关注我们吧菜菜哥&#xff0c;请教个问题呗&#xff1f;说说看&#xff0c;能否解决不敢保证哦最近做的App业务中&#xff0c;有很多敏感操作需要用户输入手机验证码这没问题&#xff0c;手机验证码主要是为了验证当前操作人的有效性&#xff0c;有什么问题呢&…

Ticket Game CodeForces - 1215D(博弈题,巴什博弈思维)

题意&#xff1a;两个人玩游戏&#xff0c;通过轮流填数字&#xff08;0~9&#xff09;&#xff0c;若最终左右两边的和相等&#xff0c;后手赢&#xff0c;否则先手赢。起始有部分数字和空格。 官方题解&#xff1a; 题解翻译&#xff1a; 让我们把余额表示为左半部分数字和…

[设计模式]合成复用原则

合成复用原则:继承和组合&#xff0c;优先使用组合。 这样写&#xff0c;每开一种车&#xff0c;就要弄一个新的Person类。 代码如下: #include <iostream> using namespace std;class AbstractCar { public:virtual void run() 0; };class DaZhong :public AbstractC…

帮 vs2019 找回丢失的 SDK

缘起 前一段时间&#xff0c;有网友遇到一个奇怪的问题&#xff0c;说他机器上的 vs2019 编译 C 工程报错。我当时一听就有两个怀疑&#xff1a;工程设置不对。vs2019 没装好。因为新建一个最简单的工程&#xff0c;编译也报一样的错误&#xff0c;所以可以排除工程设置的问题了…

java keysetview,Set——你真的了解吗?

JAVA 基础 &#xff1a;Set——你真的了解吗&#xff1f;简述Set 继承于 Collection &#xff0c;是一种集合。有元素无序、值不重复、不允许空值得特性。主要有HashSet、TreeSet两种实现方式。由于Set主要基于Map实现&#xff0c;所以特点也由Map决定。Set 结构图例如 HashSet…

Minimizing Difference CodeForces - 1244E(贪心题)

题目题意官方题解&#xff1a;百度翻译思路ac代码题意 给出一列数&#xff0c;至多n个操作使其中的数1或-1&#xff0c;要求得到最小的差值&#xff08;最大值-最小值&#xff09;&#xff1b; You are given a sequence a1_{1}1​,a2_{2}2​,…,an_{n}n​ consisting of nn …

[设计模式]依赖倒转原则

代码如下: #include <iostream> #include <string>using namespace std;//银行工作人员 class BankWorker { public:void saveService(){cout << "办理存款业务" << endl;}void payService(){cout << "办理支付业务" <&…

使用 Docker 搭建 PostgreSQL 12 主从环境

环境准备&#xff1a;一台安装了Docker的Linux服务器。为了简化演示环境&#xff0c;这里只用一台服务器来演示&#xff0c;通过不同端口来区分。01—创建一个docker bridge 网路用于测试docker network create --subnet172.18.0.0/24 dockernetwork docker network ls设置了网…

Paint the Tree CodeForces - 1244D(看似是树,其实是条链)

目录题目官方题解&#xff1a;百度翻译题解ac代码题目 给多组两顶点连接&#xff0c;得到的图任意三个顶点都是不同的颜色&#xff0c;&#xff0c;给出各顶点染三种颜色的花费&#xff0c;问各店如何染&#xff0c;满足条件情况下&#xff0c;使得花费最少&#xff1b; You …

[设计模式]简单工厂模式

简单工厂模式优点: 1.客户端和具体实现解耦 2.对于某些对象的创建过程比较复杂的情况&#xff0c;我们不用考虑这些。 简单工厂模式缺点: 1.简单工厂模式&#xff0c;增加新的功能是通过修改源代码实现&#xff0c;不符合开闭原则。 2.这个工厂(类)职责过重&#xff0c;这个…

什么前浪后浪,我们只不过是时代大潮中的一朵小浪花

这是头哥侃码的第198篇原创上周的五四青年节&#xff0c;我的朋友圈被一个短视频刷屏了&#xff0c;不知道你的朋友圈有没有被刷到&#xff1f;这个短视频来自小破站B站的一则宣传视频《后浪》。我是B站的老用户&#xff0c;所以在看完视频后&#xff0c;我特地跑到B站看了下&a…

java上GUI表格按钮,java GUI表格实例

1 package javademo;2 import java.awt.*;3 import javax.swing.*;4 import java.awt.event.*;5 public class table3{6 JFrame framenew JFrame("表格实例");7 Object [][]date {{"李明",45,"计算机"},{8 "小王",32,"数学"…

[设计模式]工厂方法模式

工厂方法模式缺点: 1.类的个数成倍增加&#xff0c;导致类越来越多&#xff0c;增加维护成本。 2.增加了系统的抽象性和理解难度。 (判断生产什么&#xff0c;又变成让客户端来判断了&#xff0c;简单工厂模式是由工厂判断) 工厂方法模式优点: 1.符合开闭原则。 简单工厂模式…

Asp.Net Boilerplate微服务实战(二)架构解析

这一章节&#xff0c;我来介绍一下Asp.Net Boilerplate框架在微服务开发中所用到的技术及其大体的组织架构。由于本系列仅讨论ABP框架在微服务架构下的应用方案&#xff0c;不涉及具体的业务逻辑&#xff0c;所以在文中&#xff0c;不讨论服务拆分方案等细节&#xff0c;也未采…

c#: 协变和逆变深度解析

环境&#xff1a;window 10.netcore 3.1vs2019 16.5.1一、为什么要有协变&#xff1f;首先看下面的代码&#xff1a;还有下面的&#xff1a;其实上面报错的是同一个问题&#xff0c;就是你无法用List<Fruit>指向List<Apple>&#xff01;我们的疑问在于&#xff0c;…