关于c#:要实用还得看-C六款最新高实用性-C开源项目分享

C# 作为一款老牌语言,尽管在热度上和流行性上和别的语言有所差距,但在 Windows 生态中,C# 依然有重要的一席之地,围绕 Windows 生态,C# 依然能领有弱小的力量。明天为大家介绍的就是六款高实用性的 C# 我的项目,一起来看看吧。 1.SiMay近程管制管理系统我的项目作者:koko 开源许可协定:AGPL-3.0 我的项目地址:https://gitee.com/dWwwang/SiMayRemoteMonitorOS 本我的项目是一个Windows近程控制系统,我的项目齐全采纳C# .NET开发,实现了基于逐行扫描算法远程桌面,桌面视图墙,文件治理,实时语音、视频监控,注册表治理,实时过程治理等性能,各模块采纳独立连贯,反对异常情况重连。 2.e-contract我的项目作者:zygforever 开源许可协定:Apache-2.0 我的项目地址:https://gitee.com/zygforever/e-contract 电子合同签订零碎,全程线上签约,秒速签订,反对PC、手机、微信等多终端签订, 随时随地签合同。 3.WeaveNet我的项目作者:dreamsfly 开源许可协定:Apache-2.0 我的项目地址:https://gitee.com/UDCS/WeaveAI 应用C#编写的用于神经网络的计算图框架computational graph。带cnn,bp,fcn,lstm,convlstm等示例。应用办法靠近pytorch。 4.SourceGit我的项目作者:leo 开源许可协定:MIT 我的项目地址:https://gitee.com/sourcegit/SourceGit 开源的Git客户端,仅用于Windows 10。单文件,无需装置,< 500KB。 5.Quartz.NetUI我的项目作者:x_discoverer 开源许可协定:MIT 我的项目地址:https://gitee.com/x_discoverer/Quartz.NetUI 基于.NetCore + Quartz.Net + Vue 开箱即用的定时工作UI。简直没有上手难度,不依赖数据库,只需在界面做简略配置。 6.FastTunnel我的项目作者:SpringHgui 开源许可协定:Apache-2.0 我的项目地址:https://gitee.com/Hgui/FastTunnel FastTunnel是一款高性能跨平台内网穿透工具,应用它能够实现在公网上拜访您的内网服务。 如果你想去 Gitee 上看看更多 C# 我的项目,那就点击前面的链接返回 Gitee 吧:https://gitee.com/explore/all?lang=csharp

July 27, 2020 · 1 min · jiezi

关于c#:一个有趣的问题-你知道SqlDataAdapter中的Fill是怎么实现的吗

一:背景1. 讲故事最近因为各方面起因换了一份工作,去了一家主营物联柜的公司,有意思的是物联柜上的终端是用 wpf 写的,代码也算是年久失修,感觉技术债还是蛮重的,前几天在调试一个bug的时候,看到了一段相似这样的代码: var dt = new DataTable(); SqlDataAdapter adapter = new SqlDataAdapter(new SqlCommand()); adapter.Fill(dt);是不是很眼生哈,或者你也曾经多年不见了,犹记得那时候为了能从数据库获取数据,第一种办法就是采纳 SqlDataReader 一行一行从数据库读取,而且还要操心 Reader 的 close 问题,第二种办法为了防止麻烦,就间接应用了本篇说到的 SqlDataAdapter ,简略粗犷,啥也不必操心,对了,不晓得您是否和我一样对这个 Fill 办法很好奇呢?,它是如何将数据塞入到 DataTable 中的呢? 也是用的 SqlDataReader 吗? 而且 Fill 还有好几个扩大办法,哈哈,本篇就一一聊一聊,就当回顾经典啦! 二:对Fill办法的探索1. 应用 dnspy 查看Fill源码dnspy小工具大家能够到GitHub下面去下载一下,这里就不具体说啦,接下来追一下Fill的最上层实现,如下代码: public int Fill(DataTable dataTable) { IntPtr intPtr; Bid.ScopeEnter(out intPtr, "<comm.DbDataAdapter.Fill|API> %d#, dataTable\n", base.ObjectID); int result; try { DataTable[] dataTables = new DataTable[] { dataTable }; IDbCommand selectCommand = this._IDbDataAdapter.SelectCommand; CommandBehavior fillCommandBehavior = this.FillCommandBehavior; result = this.Fill(dataTables, 0, 0, selectCommand, fillCommandBehavior); } finally { Bid.ScopeLeave(ref intPtr); } return result; }下面的代码比拟要害的一个中央就是 IDbCommand selectCommand = this._IDbDataAdapter.SelectCommand; 这里的 SelectCommand 来自于哪里呢? 来自于你 new SqlDataAdapter 的时候塞入的构造函数 SqlCommand,如下代码: ...

July 22, 2020 · 3 min · jiezi

关于c#:C基础知识

更多详情见文档:C# 微软文档C#教程 C语言网 C#为强类型语言,但在C#4.0后引入了动静类型var,来模拟js的弱类型。Consloe.WriteLine("Hello World"); Console类属于System命名空间。命名空间作用:用于被别的我的项目援用,他人只需using namespace就可引入。(相似java中的package)传援用:ref,out。partial关键字:定义一个类的某局部。(很少润饰办法) 拜访润饰附、修饰符: 类:类的拜访修饰符(他人工程是否能拜访该类):public、internal或者不写。修饰符(形容类自身):abstract、sealed、static。sealed不能被继承。办法:拜访修饰符:public、internal、private(缺省时)修饰符:abstract、static、sealed + override + virtual类依赖于命名空间,命名空间依赖于类库(dll) using System;namespace HelloWorld{ class Program{ static void main(string[] args){ Console.WriteLine("hello,world!"); } }}根底数据类型 int x= 2;long x= 2L;float x= 2F;double x= 2D;或者间接2char x= 'a';string x= "hello";bool x= true;sting x= null;const申明常量(相似java中的final)空联合运算符 a??b 若a为null,则返回b五类数据类型类(class)、构造体(struct)、枚举(enum)、接口、委托图片中蓝色字体为根本数据类型,虚线下为定义类型的关键字。f(x):委托typeof:类型;GetType().Namechecked()查看异样并抛出,在try catch中捕捉unchecked()不查看

July 20, 2020 · 1 min · jiezi

关于c#:C队列Queue解说及使用

有一个场景:一个抢购的我的项目,假如有5件产品,谁先抢到谁可能买,然而如果此时此刻(这儿的此时此刻假如是雷同的时刻),有100人去抢这个产品,如果使用平时的方法会呈现什么状况呢?你懂的,这儿所说是便是无关并发的问题。 平时咱们去超市购物去结账的时候便是排队,这儿咱们先让抢购人排好队,按时刻,谁先点击的抢购按钮谁就排在后面,这样就造成了一个行列,而后咱们再对这个行列解决,这样就不会呈现并发的问题了。(至多可能解决这样简略的并发,这儿不评论太简单的并发) 事例: 要求:有一个公布文章的接口,每公布一篇文章,调用一下接口。(这儿不用批量公布,为理解说这个) 建设一个这样的处理程序类,BusinessInfoHelper.csC# [C#]纯文本查看 [/size] [size=3]namespaceMyNameSpace { //行列长期类 publicclassQueueInfo { publicstringmedias{get;set;} publicstringproids{get;set;} publicstringhost{get;set;} publicstringuserid{get;set;} publicstringfeedid{get;set;} } publicclassBusinessInfoHelper { #region解决公布时含有优质媒体时,前台页面卡住的景象 //原理:使用生产者消费者模式进行入列出列操作 publicreadonlystaticBusinessInfoHelperInstance=newBusinessInfoHelper(); privateBusinessInfoHelper() {} privateQueueListQueue=newQueue(); publicvoidAddQueue(stringmedias,stringproids,stringhost,stringuserid,stringfeedid)//入列 { QueueInfoqueueinfo=newQueueInfo(); queueinfo.medias=medias; queueinfo.proids=proids; queueinfo.host=host; queueinfo.userid=userid; queueinfo.feedid=feedid; ListQueue.Enqueue(queueinfo); } publicvoidStart()//启动 { Threadthread=newThread(threadStart); thread.IsBackground=true; thread.Start(); } privatevoidthreadStart() { while(true) { if(ListQueue.Count>0) { try { ScanQueue(); } catch(Exceptionex) { LO_LogInfo.WLlog(ex.ToString()); } } else { //没有使命,休憩3秒钟 Thread.Sleep(3000); } } } //要执行的方法 privatevoidScanQueue() { while(ListQueue.Count>0) { try { //从行列中取出 QueueInfoqueueinfo=ListQueue.Dequeue(); //取出的queueinfo就可能用了,外面有你要的货色 //以下便是处理程序了 //。。。。。。 } catch(Exceptionex) { throw; } } } #endregion } } 以上页面写好后,在程序开始运行时就得启动这个线程去一直的解决使命,那么咱们在Global的Application_Start里可能这样写: [C#]纯文本查看 //启动公布优质媒体程序 MyNameSpace.BusinessInfoHelper.Instance.Start(); 有一个问题进去了,如果我解决完行列中的一条记录后,想返回这条记录的ID,这个程序如同不能完结,我就使用了另一个方法Lock方法,把方法确定,具体的如下: 在页面中界说全局的锁: [C#]纯文本查看 privatestaticobjectlockObject=newObject(); 在方法中这样调用: [C#]纯文本查看 lock(lockObject) { //…….. } 如果不使用第二种方法的全局锁,不知各位大侠有没有好的解决办法,如果有,可能跟贴,非常感谢! ...

July 18, 2020 · 1 min · jiezi

Winform作为server-使用SignalR

Winform作为server1.增加nugetMicrosoft.AspNet.SignalR.SelfHostMicrosoft.Owin.Cors其中:Microsoft.AspNet.SignalR.SelfHost 包外面蕴含SignalR类所以除了这两个不须要任何其余包Microsoft.Owin.Cors 解决跨域问题 2.创立Hubusing Microsoft.AspNet.SignalR;//Hub的别名,不便前台调用//[HubName("abc")]public class MyHub : Hub{ /// <summary> /// 编写发送信息的办法 /// </summary> /// <param name="name"></param> /// <param name="message"></param> // [HubMethodName("send")] public void Send(string name, string message) { //调用所有客户注册的本地的JS办法(addMessage) Clients.All.addMessage(name, message); //调用以后客户注册的本地的JS办法(addMessage) Clients.Caller.addMessage(name, message); }}3.注册中间件 代码 using Microsoft.Owin;using Microsoft.Owin.Cors;using Owin;[assembly: OwinStartup(typeof(SignalRServer.Startup))]namespace SignalRServer{ public class Startup { public void Configuration(IAppBuilder app) { //跨域 app.UseCors(CorsOptions.AllowAll); ////配置生成代理 app.MapSignalR(); // app.MapSignalR("/client", new HubConfiguration()); //app.Map("/messageHub", map => //{ // map.RunSignalR(new Microsoft.AspNet.SignalR.HubConfiguration { EnableJavaScriptProxies = true }); //}); //app.Map("/messageConnection", map => //{ // map.RunSignalR<MessageConnection>(); //}); } }}4.开启服务 string ServerUri="http://localhost:8000/"; IDisposable SignalR = WebApp.Start(ServerUri);能够应用线程来开启 ...

July 15, 2020 · 1 min · jiezi

C生成唯一时间戳ID代码分享

/// ///时刻戳ID /// publicclassTimestampID { privatelong_lastTimestamp; privatelong_sequence;//计数从零开始 privatereadonlyDateTime?_initialDateTime; privatestaticTimestampID_timestampID; privateconstintMAX_END_NUMBER=9999; privateTimestampID(DateTime?initialDateTime)C# { _initialDateTime=initialDateTime; } /// ///获取单个实例对象 /// ///开始时刻,与当前时刻做个相差取时刻戳 /// publicstaticTimestampIDGetInstance(DateTime?initialDateTime=null) { if(_timestampID==null)Interlocked.CompareExchange(ref_timestampID,newTimestampID(initialDateTime),null); return_timestampID; } /// ///开始时刻,作用时刻戳的相差 /// protectedDateTimeInitialDateTime { get { if(_initialDateTime==null||_initialDateTime.Value==DateTime.MinValue)returnnewDateTime(1970,1,1,0,0,0,DateTimeKind.Utc); return_initialDateTime.Value; } } /// ///获取时刻戳ID /// /// publicstringGetID() { longtemp; vartimestamp=GetUniqueTimeStamp(_lastTimestamp,outtemp); return$”{timestamp}{Fill(temp)}”; } privatestringFill(longtemp) { varnum=temp.ToString(); IListchars=newList(); for(inti=0;i<MAX_END_NUMBER.ToString().Length-num.Length;i++) { chars.Add(‘0’); } returnnewstring(chars.ToArray())+num; } /// ///获取一个时刻戳字符串 /// /// publiclongGetUniqueTimeStamp(longlastTimeStamp,outlongtemp) { lock(this) { temp=1; vartimeStamp=GetTimestamp(); if(timeStamp==_lastTimestamp) { _sequence=_sequence+1; temp=_sequence; if(temp>=MAX_END_NUMBER) { timeStamp=GetTimestamp(); _lastTimestamp=timeStamp; temp=_sequence=1; } } else { _sequence=1; _lastTimestamp=timeStamp; } returntimeStamp; } } /// /// /// /// privatelongGetTimestamp() { if(InitialDateTime>=DateTime.Now)thrownewException(“开始时刻比当前时刻还大,不合理”); varts=DateTime.UtcNow-InitialDateTime; return(long)ts.TotalMilliseconds; } } ...

June 28, 2020 · 1 min · jiezi

C-90-终于来了-Toplevel-programs-和-Partial-Methods-两大新特性探究

一:背景1. 讲故事.NET 5 终于在 6月25日 发布了第六个预览版,随之而来的是更多的新特性加入到了 C# 9 Preview 中,这个系列也可以继续往下写了,废话不多说,今天来看一下 Top-level programs 和 Extending Partial Methods 两大新特性。 2. 安装必备下载最新的 .net 5 preview 6。 下载最新的 Visual Studio 2019 version 16.7 Preview 3.1 二:新特性研究1. Top-level programs如果大家玩过 python,应该知道在 xxx.py 中写一句 print,这程序就能跑起来了,简单高效又粗暴,很开心的是这特性被带到了C# 9.0 中。 修改前using System;namespace ConsoleApp2{ class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); } }修改后System.Console.WriteLine("Hello World!"); 这就有意思了,Main入口函数去哪了? 没它的话,JIT还怎么编译代码呢? 想知道答案的话用 ILSpy 反编译看一下就好啦! .class private auto ansi abstract sealed beforefieldinit $Program extends [System.Runtime]System.Object{ .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Methods .method private hidebysig static void $Main ( string[] args ) cil managed { // Method begins at RVA 0x2050 // Code size 18 (0x12) .maxstack 8 .entrypoint IL_0000: ldstr "Hello World!" IL_0005: call void [System.Console]System.Console::WriteLine(string) IL_000a: nop IL_000b: call string [System.Console]System.Console::ReadLine() IL_0010: pop IL_0011: ret } // end of method $Program::$Main} // end of class $Program从 IL 上看,类变成了 $Program, 入口方法变成了 $Main, 这就好玩了,在我们的印象中入口函数必须是 Main,否则编译器会给你一个大大的错误,你加了一个 $ 符号,那CLR还能认识吗? 能不能认识我们用 windbg 看一些托管和非托管堆栈,看看有什么新发现。 ...

June 27, 2020 · 2 min · jiezi

Newtonsoft-六个超简单又实用的特性值得一试-上篇

一:讲故事看完官方文档,阅读了一些 Newtonsoft 源码,对它有了新的认识,先总结 六个超经典又实用的特性,同大家一起分享,废话不多说,快来一起看看吧~~~ 二:特性分析1. 代码格式化如果你直接使用 JsonConvert.SerializeObject的话,默认情况下所有的json是挤压在一块的,特别不方便阅读,如下所示: static void Main(string[] args) { var reportModel = new ReportModel() { ProductName = "法式小众设计感长裙气质显瘦纯白色仙女连衣裙", TotalPayment = 100, TotalCustomerCount = 2, TotalProductCount = 333 }; var json = JsonConvert.SerializeObject(reportModel); System.Console.WriteLine(json); } } public class ReportModel { public string ProductName { get; set; } public int TotalCustomerCount { get; set; } public decimal TotalPayment { get; set; } public int TotalProductCount { get; set; } } ...

June 21, 2020 · 2 min · jiezi

经验栈C监测IPv4v6网速及流量

1、前言 最近做项目需要用到监测网速及流量,我经过百度和墙内谷歌都没能快速发现监测IPV6流量和网速的用例;也经过自己的一番查询和调试,浪费了不少时间,现在作为经验分享出来希望大家指正。 2、C#代码using System.Net.NetworkInformation;using System.Timers;namespace Monitor{ public class MonitorNetwork { public string UpSpeed { get; set; } public string DownSpeed { get; set; } public string AllTraffic { get; set; } private string NetCardDescription { get; set; } //建立连接时上传的数据量 private long BaseTraffic { get; set; } private long OldUp { get; set; } private long OldDown { get; set; } private NetworkInterface networkInterface { get; set; } private Timer timer = new Timer() { Interval = 1000 }; public void Close() { timer.Stop(); } public MonitorNetwork(string netCardDescription) { timer.Elapsed += Timer_Elapsed; NetCardDescription = netCardDescription; timer.Interval = 1000; } public bool Start() { networkInterface = null; NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); foreach (var var in nics) { if (var.Description.Contains(NetCardDescription)) { networkInterface = var; break; } } if (networkInterface == null) { return false; } else { BaseTraffic = (networkInterface.GetIPStatistics().BytesSent + networkInterface.GetIPStatistics().BytesReceived); OldUp = networkInterface.GetIPStatistics().BytesSent; OldDown = networkInterface.GetIPStatistics().BytesReceived; timer.Start(); return true; } } private string[] units = new string[] {"KB/s","MB/s","GB/s" }; private void CalcUpSpeed() { long nowValue = networkInterface.GetIPStatistics().BytesSent; int num = 0; double value = (nowValue - OldUp) / 1024.0; while (value > 1023) { value = (value / 1024.0); num++; } UpSpeed = value.ToString("0.0") + units[num]; OldUp = nowValue; } private void CalcDownSpeed() { long nowValue = networkInterface.GetIPStatistics().BytesReceived; int num = 0; double value = (nowValue - OldDown) / 1024.0; while (value > 1023) { value = (value / 1024.0); num++; } DownSpeed = value.ToString("0.0") + units[num]; OldDown = nowValue; } private string[] unitAlls = new string[] { "KB", "MB", "GB" ,"TB"}; private void CalcAllTraffic() { long nowValue = OldDown+OldUp; int num = 0; double value = (nowValue- BaseTraffic) / 1024.0; while (value > 1023) { value = (value / 1024.0); num++; } AllTraffic = value.ToString("0.0") + unitAlls[num]; } private void Timer_Elapsed(object sender, ElapsedEventArgs e) { CalcUpSpeed(); CalcDownSpeed(); CalcAllTraffic(); } }}3、胡说八道 虽然没能直接快速地百度到方法,但是实现这个需求的时候,心里是有个谱,Windows系统能监测到这个网速和流量,没理由实现不了,只需要一个方法将这个信息读取出来就好。最后实现这个需求是利用了System.Net.NetworkInformation这个程序集,但是这个程序集没有只接提供网速监测的方法,而是提供了接收和发送数据量的属性,需要自己计算出即使网速,所以这个网速不是特别的准确。 ...

June 18, 2020 · 2 min · jiezi

技术栈CRC校验原理及C代码实现CRC16CRC32计算FCS校验码

1.CRC、FCS是什么CRC,全称Cyclic Redundancy Check,中文名称为循环冗余校验,是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种信道编码技术,主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。 FCS,全称Frame Check Sequence,中文名称为帧校验序列,俗称帧尾,即计算机网络数据链路层的协议数据单元(帧)的尾部字段,是一段4个字节的循环冗余校验码。 注:CRC循环冗余校验和FCS帧校验序列是单独的概念,CRC是一种错误校验方法,FCS是帧尾校验码,FCS可以采用CRC校验方法,也可以采用其他校验方法。 2.CRC算法原理我们可以把任意的一串二进制数据表示为一个与之对应的多项式。比如: 二进制数据:1100101 多项式:$x^6 + x^5 + x^2+1$多项式: $x^6 + x^4+x^3 + x^2+1$二进制数据:1011101有了这样的对应关系,对二进制数据的CRC校验就可以利用多项式运算规则进行校验计算。CRC校验算法正是采用了模2除法,在数据处理里的具体表现为异或运算。 CRC的具体运算规则为:假设要传输的二进制数据为:10010110,对应的m阶多项式为:$M =x^7+x^4+x^2+x^1$,除数为h阶的多项式为:$H=x^4+x$,对应的二进制码为:10010,先将M乘以$x^h$,即将M对应的二进制数据后面加h个0,然后除以h阶的多项式H,得到的h-1阶的余数项R对应的二进制数据即为数据10010110的CRC校验码。 3.计算CRC校验3.1.手工计算CRC校验码M和H的多项式除法运算,可以用模2除法运算计算。下面为以生成多项式为H求10010110的CRC校验码运算过程: 对应到异或运算:通过示例即其他自定义的一些数据运算后,根据运算现象总结可以得到一些规律: 1.每次异或运算,当从左到右首位为1的时候,就与生成多项式H异或运算,然后再左移1位;当首位为0的时候只将数据左移1位。 2.每次异或运算后的数据,首位必定为0,因为首位为1的时候进行异或运算,而生成多项式的首位也必定为1。所以当需要进行异或运算时,可以舍弃H的首位,舍弃后为H',直接将数据左移一位后再与H'异或。3.每次运算,参与运算的是数据的前h位,可以用一个存储h位二进制数据的寄存器S,将数据的前h位存储到这个寄存器中。每次运算先将寄存器的首位移除,然后将二进制数据后一位移入,然后再参与运算,最后寄存器中的值即为CRC校验码。 3.2.C#代码计算CRC校验码//代码验证如下:static void Main(string[] args){ int data = 0b10010110; int ploy = 0b0010; ploy <<= 4; Console.WriteLine($"第0次运算结果:"+Convert.ToString(data, 2)); for (int i = 0; i < 8; i++) { if ((data & 0b10000000) == 0b10000000) { data = (data << 1) ^ ploy; } else { data <<= 1; } Console.WriteLine($"第{i+1}次运算结果:"+Convert.ToString(data, 2)); } Console.WriteLine($" 最终运算结果:"+Convert.ToString(data, 2)); Console.ReadKey();} ...

June 18, 2020 · 4 min · jiezi

C默认参数原理探究

起因写这一篇的起因是想要通过新增默认参数来代替以前的方法,结果发现尽管在调用时写起来一样,实际上也没有被当做同样的方法,两个方法大致如下: // 先前的方法-删除private static string TestMethod(string first){ return first;}// 新增的同名方法private static string TestMethod(string first, string second = "2"){ return second;}上述两种方法都可以通过 TestMethod("1"); 调用,所以最开始误以为两个方法的调用是等价的,但是实际使用中通过DLL引用的方式会提示找不到方法,这里就出现了问题。 首先我们可以进行一个尝试,会发现这两个方法可以同时存在,还是上面的例子,这时再通过 TestMethod("1"); 调用会发现返回的结果是“1”,也就是第一个没有默认参数的方法 到这里为止,暂时还不太清楚原理,但是可以感觉到调用时程序中的写法可能是区别的,这时候我们可以再深入一点,通过中间语言IL(Intermediate Language)的角度去看一下 ILSpy借助一个简单的例子,先用常用的反编译工具看一下 static void Main(string[] args){ Console.WriteLine(TestMethod("1")); Console.ReadKey(); TestMethodWithDefaultParam(string.Empty);}private static string TestMethod(string first){ return first;}private static string TestMethod(string first, string second = "2"){ return second;}private static void TestMethodWithDefaultParam(string first, string second = "2"){}把编译的好的程序放到ILSpy里面反编译看下 重点对比看下 TestMethodWithDefaultParam 这个方法的调用,可以发现虽然我们没有传入第二个参数,但是由于默认参数的存在,编译器自动帮我们补上了一个参数,而 TestMethod 方法则明显是调用第一个没有默认参数的,有默认参数的 TestMethod 方法被忽略了 ...

June 16, 2020 · 1 min · jiezi

MySql轻松入门系列第二站-使用visual-studio-对mysql进行源码级调试

一:背景1. 讲故事上一篇说了mysql的架构图,很多同学反馈说不过瘾,毕竟还是听我讲故事,那这篇就来说一说怎么利用visual studio 对 mysql进行源码级调试,毕竟源码面前,不谈隐私,圣人面前,皆为蝼蚁。 二:工具合集mysql是C++写的,要想在windows上编译,还需要下载几个必备小工具。 mysql-5.7.12.zipcmake-3.17.3-win64-x64.msiboost_1_59_0.tar.gzbison-2.4.1-setup.exewindows 10 x64这里简单说一下:可以用cmake 将源码生成 *.sln 可打开的解决方案,比如可以通过它最终生成 MySQL.sln。boost 是C++中非常强大的基础库, bison 一个流行的语法分析器程序,用于给mysql提供语法分析,最后就是下载正确的mysql版本 5.7.12。 三. 详细安装我会写的比较细,毕竟我也花了一下午时间,寒酸(┬_┬) 1. cmake-3.17.3-win64-x64.msi 和 bison-2.4.1-setup.execmake 和 bison 安装起来比较方便,一键安装就可以了,不过这里有一个大坑注意了,在安装Bison的时候,千万不要使用默认路径,因为默认路径有空格,会导致你后面vs编译的时候卡住,又不显示什么原因,可气!!! 所以我换成自定义的: C:\2\GnuWin32。 最后确保 cmake 和 bison 的bin文件都在 环境变量中即可。 2. mysql-5.7.12.zip这里我用 C:\2作为根文件夹,所有的小工具都在这里,如图: 接下来将 mysql-5.7.12.zip 解压一下,然后进入解压后的文件夹,新建一个boost文件夹,将boost_1_59_0.tar.gz放入其中,然后再新建一个 brelease 文件夹可用于存放最终生成的MySql.sln。???。 3. cmake编译都准备好了之后,可以开始cmake编译了。 PS C:\2\mysql-5.7.12\brelease> cmake .. -DDOWNLOAD_BOOST=1 -DWITH_BOOST="C:\2\mysql-5.7.12\boost\boost_1_59_0.tar.gz"-- Building for: Visual Studio 16 2019CMake Deprecation Warning at CMakeLists.txt:26 (CMAKE_POLICY): The OLD behavior for policy CMP0018 will be removed from a future version of CMake.-- Cannot find wix 3, installer project will not be generated-- COMPILE_DEFINITIONS: _WIN32_WINNT=0x0601;WIN32_LEAN_AND_MEAN;NOGDI;NOMINMAX;HAVE_CONFIG_H-- CMAKE_C_FLAGS: /DWIN32 /D_WINDOWS /W3 /MP /wd4800 /wd4805 /wd4996-- CMAKE_CXX_FLAGS: /DWIN32 /D_WINDOWS /W3 /GR /EHsc /MP /wd4800 /wd4805 /wd4996 /we4099-- CMAKE_C_FLAGS_DEBUG: /MTd /Z7 /Ob1 /Od /RTC1 /EHsc -DENABLED_DEBUG_SYNC -DSAFE_MUTEX-- CMAKE_CXX_FLAGS_DEBUG: /MTd /Z7 /Ob1 /Od /RTC1 /EHsc -DENABLED_DEBUG_SYNC -DSAFE_MUTEX-- CMAKE_C_FLAGS_RELWITHDEBINFO: /MT /Z7 /O2 /Ob1 /DNDEBUG /EHsc -DDBUG_OFF-- CMAKE_CXX_FLAGS_RELWITHDEBINFO: /MT /Z7 /O2 /Ob1 /DNDEBUG /EHsc -DDBUG_OFF-- Configuring done-- Generating done-- Build files have been written to: C:/2/mysql-5.7.12/brelease当看到最后一句 Build files have been written to: C:/2/mysql-5.7.12/brelease,恭喜你,MySQL.sln生成好了。 ...

June 10, 2020 · 2 min · jiezi

一个static和面试官扯了一个小时舌战加强版

一:背景1. 讲故事最近也是奇怪,在社区里看到好几篇文章聊static 的玩法以及怎么拿这个和面试官扯半个小时,有点意思,点进去看都是java版的,这就没意思了,怎么也得有一篇和面试官扯C# 中的 static用法撒,既然没有人开这个头,那我就献丑了。。。,下面以QA的方式记述,大家可以代入一下能回答几个问题。 二:QA环节面试官: 请问您都是在什么场景下用static的?解析: 可能面试官潜意识的想问问你会不会使用本地缓存。 码农: 先不说我的场景,纵观C#的底层FCL源码,你会发现很多的 static修饰的集合,如ThreadPool: [SecurityCritical] private static bool QueueUserWorkItemHelper(WaitCallback callBack, object state, ref StackCrawlMark stackMark, bool compressStack) { QueueUserWorkItemCallback callback = new QueueUserWorkItemCallback(callBack, state, compressStack, ref stackMark); ThreadPoolGlobals.workQueue.Enqueue(callback, forceGlobal: true); result = true; }其中的 workQueue 就是一个静态队列,不仅如此还有Quartz底层自研的线程池,还有web中的Session,Application,无非就是想用static做一个池化技术和AppDomain级的本地缓存,所以我的应用场景也无非是这些了。 面试官: 您会几种实现单例的方式?解析:既然面试官想和你扯static,就是想看看你会不会用 static cctor静态构造器构建单例! 码农: 实不相瞒,不管是用懒汉式还是饿汉式,大体上也就这几种 双检锁, static cctor, Lazy<T>, 不知道您想让我细说哪一种?面试官: 那就说一下静态构造函数为什么可以实现单例?解析: 可能觉得码农回答的有点拽,问深一点看看是不是唬人的。 码农:说到单例,每一个人都会提到在多线程场景下的并发问题导致多个单例的尴尬,所以有了给代码加上各种花哨的锁,比如刚才我提到的双检索,所以说没有锁。。。这个问题是搞不定的,换句话说 静态构造函数 也是用了锁机制。面试官: 你确定用到了锁? 有证据吗?解析: 有戏了,对你产生感兴趣了,愿听其详。 码农: 既然要证据,那我先构思一段如下代码: class Program { static void Main(string[] args) { Person person = new Person(); Console.ReadLine(); } } class Person { static Person() { Console.WriteLine("正在处理静态函数"); Console.ReadLine(); } }然后抓一个dump文件,用windbg 看一下主线程的托管和非托管堆栈。 ...

June 7, 2020 · 2 min · jiezi

字符串太占内存了我想了各种奇思淫巧对它进行压缩

一:背景1. 讲故事在我们的一个全内存项目中,需要将一家大品牌店铺小千万的trade灌入到内存中,大家知道trade中一般会有订单来源,省市区 ,当把这些字段灌进去后,你会发现他们特别侵蚀内存,因为都是字符串类型,不知道大家对内存侵蚀性是不是很清楚,我就问一个问题。 Answer: 一个空字符串占用多大内存? 你知道吗?思考之后,下面我们就一起验证下,使用windbg去托管堆一查究竟,代码如下: static void Main(string[] args) { string s = string.Empty; Console.ReadLine(); }0:000> !clrstack -lOS Thread Id: 0x308c (0) Child SP IP Call SiteConsoleApp6.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp6\Program.cs @ 19] LOCALS: 0x00000087391febd8 = 0x000002605da914200:000> !DumpObj /d 000002605da91420Name: System.StringString: Fields: MT Field Offset Type VT Attr Value Name00007ff9eb2b85a0 4000281 8 System.Int32 1 instance 0 m_stringLength00007ff9eb2b6838 4000282 c System.Char 1 instance 0 m_firstChar00007ff9eb2b59c0 4000286 d8 System.String 0 shared static Empty >> Domain:Value 000002605beb2230:NotInit <<0:000> !objsize 000002605da91420sizeof(000002605da91420) = 32 (0x20) bytes (System.String) ...

June 4, 2020 · 3 min · jiezi

c-路径的截取文件名称截取后缀名截取不写split不用substring

string filePath = @"E:\Randy0528\中文目录\JustTest.rar";Response.Write("文件路径:"+filePath);Response.Write("<br/>更改路径字符串的扩展名。<br/>");Response.Write(System.IO.Path.ChangeExtension(filePath, "txt"));Response.Write("<br/>返回指定路径字符串的目录信息。。<br/>");Response.Write(System.IO.Path.GetDirectoryName(filePath));Response.Write("<br/>返回指定的路径字符串的扩展名。<br/>");Response.Write(System.IO.Path.GetExtension(filePath));Response.Write("<br/>返回指定路径字符串的文件名和扩展名。<br/>");Response.Write(System.IO.Path.GetFileName(filePath));Response.Write("<br/>返回不具有扩展名的指定路径字符串的文件名。<br/>");Response.Write(System.IO.Path.GetFileNameWithoutExtension(filePath));Response.Write("<br/>获取指定路径的根目录信息。<br/>");Response.Write(System.IO.Path.GetPathRoot(filePath));Response.Write("<br/>返回随机文件夹名或文件名。<br/>");Response.Write(System.IO.Path.GetRandomFileName());Response.Write("<br/>创建磁盘上唯一命名的零字节的临时文件并返回该文件的完整路径。<br/>");Response.Write(System.IO.Path.GetTempFileName());Response.Write("<br/>返回当前系统的临时文件夹的路径。<br/>");Response.Write(System.IO.Path.GetTempPath());Response.Write("<br/>确定路径是否包括文件扩展名。<br/>");Response.Write(System.IO.Path.HasExtension(filePath));Response.Write("<br/>获取一个值,该值指示指定的路径字符串是包含绝对路径信息还是包含相对路径信息。<br/>");Response.Write(System.IO.Path.IsPathRooted(filePath));执行结果文件路径:E:\Randy0528\中文目录\JustTest.rar更改路径字符串的扩展名。E:\Randy0528\中文目录\JustTest.txt返回指定路径字符串的目录信息。。E:\Randy0528\中文目录返回指定的路径字符串的扩展名。.rar返回指定路径字符串的文件名和扩展名。JustTest.rar返回不具有扩展名的指定路径字符串的文件名。JustTest获取指定路径的根目录信息。E:\返回随机文件夹名或文件名。ct2h5b2h.sed创建磁盘上唯一命名的零字节的临时文件并返回该文件的完整路径。C:\Documents and Settings\Randy\Local Settings\Temp\tmpAD.tmp返回当前系统的临时文件夹的路径。C:\Documents and Settings\Randy\Local Settings\Temp\确定路径是否包括文件扩展名。True获取一个值,该值指示指定的路径字符串是包含绝对路径信息还是包含相对路径信息。True————————————————版权声明:本文为CSDN博主「李硕`丹诗尔顿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/us2019/article/details/89475439

June 2, 2020 · 1 min · jiezi

MySql轻松入门系列第一站-从源码角度轻松认识mysql整体框架图

一:背景1. 讲故事最近看各大技术社区,不管是知乎,掘金,博客园,csdn基本上看不到有小伙伴分享sqlserver类的文章,看样子这些年sqlserver没落了,已经后继无人了,再写sqlserver是不可能再写了,这辈子都不会写了,只能靠技术输出mysql维持生活这样子。 二:了解架构图mysql最大的好处就是开源, 手握百万源码,有什么问题搞不定呢? 这一点要比sqlserver爽多了,不用再dbcc捣来捣去。 1. 从架构图入手大家都知道做/装修房子都要有一张图纸,其实软件也是一样,只要有了这么一张图纸,大方向就定下来了,再深入到细节也不会乱了方向,然后给大家看一下我自己画的架构图,画的不对请轻拍。 其实SqlServer,Oracle,MySql架构都大同小异,MySql的鲜明特点就是存储引擎做成了插拔式,这就牛逼了,现行最常用的是InnoDB,这就让我有了一个想法,有一套业务准备用 InMemory 模式跑一下,厉害了~~~ 2. 功能点介绍MySql其实就两大块,一块是MySql Server层,一块就是Storage Engines层。 <1> Client不同语言的sdk遵守mysql协议就可以与mysqld进行互通。 <2> Connection/Thread PoolMySql使用C++编写,Connection是非常宝贵的,在初始化的时候维护一个池。 <3> SqlInterface,Parse,Optimizer,Cache对sql处理,解析,优化,缓存等处理和过滤模块,了解了解即可。 <4> Storage Engines负责存储的模块,官方,第三方,甚至是你自己都可以自定义实现这个数据存储,这就把生态做起来了,??。 三: 源码分析关于怎么去下载mysql源码,这里就不说了,大家自己去官网捣鼓捣鼓哈,本系列使用经典的 mysql 5.7.14版本。 1. 了解mysql是如何启动监听的手握百万行源码,怎么找入口函数呢??? ???,其实很简单,在mysqld进程上生成一个dump文件,然后看它的托管堆不就好啦。。。 从图中可以看到,入口函数就是 mysqld!mysqld_main+0x227 中的 mysqld_main, 接下来就可以在源码中全文检索下。 <1> mysqld_main 入口函数 => sql/main.ccextern int mysqld_main(int argc, char **argv);int main(int argc, char **argv){ return mysqld_main(argc, argv);}这里大家可以用visualstudio打开C++源码,使用查看定义功能,非常好用。 <2> 创建监听int mysqld_main(int argc, char **argv){ //创建服务监听线程 handle_connections_sockets();}void handle_connections_sockets(){ //监听连接 new_sock= mysql_socket_accept(key_socket_client_connection, sock, (struct sockaddr *)(&cAddr), &length); if (mysql_socket_getfd(sock) == mysql_socket_getfd(unix_sock)) thd->security_ctx->set_host((char*) my_localhost); //创建连接 create_new_thread(thd);}//创建新线程处理处理用户连接static void create_new_thread(THD *thd){ thd->thread_id= thd->variables.pseudo_thread_id= thread_id++; //线程进了线程调度器 MYSQL_CALLBACK(thread_scheduler, add_connection, (thd)); }至此mysql就开启了一个线程对 3306 端口进行监控,等待客户端请求触发 add_connection 回调。 ...

June 2, 2020 · 2 min · jiezi

算法递归

一、递归的定义若在一个函数、过程或者数据结构定义的内部,直接(或间接)出现定义本身的应用,则称它们是递归的,或者是递归定义的。递归函数是指一个直接调用自己或通过一系列的调用语句间接调用自己的函数。计算机是用栈来记录每个调用中的函数。这个栈就叫作调用栈:1、调用函数时,系统将为调用者构造一个由参数表和返回地址组成的活动记录,并将其压入到由系统提供的运行时刻栈的栈顶,然后将程序的控制权转移到被调函数。若被调函数有局部变量,则在运行时刻,在栈的栈顶也要为其分配相应的空间。因此,活动记录和这些局部变量形成了一个可供被调函数使用的活动结构。2、被调函数执行完毕时,系统将运行时刻栈的栈顶的活动结构退栈,并根据退栈的活动结构中所保存的返回地址将程序的控制权转移给调用者继续执行。 二、递归算法的设计步骤1、将规模较大的原问题分解为一个或多个规模更小、但具有类似于原问题特性的子问题;2、确定一个或多个无须分解、可直接求解的最小子问题(称为递归的终止条件或基准情形)。例如,非负整数n的阶乘可递归定义为: 三、递归算法的优点1、递归是一种强有力的数学工具,它可使问题的描述和求解变得简捷和清晰。2、递归算法常常比非递归算法更容易设计,尤其是当问题本身或所涉及的数据结构是递归定义时,使用递归算法特别合适。3、有些问题用递归算法实现既巧妙又高效。 四、递归算法的缺点大多数情况下,它并不比普通的循环更加优雅、高效; 五、递归算法的适用场景1、递归十分适用于那些无法预估计算深度的问题,比如遍历文件系统。假设你现在要写一个脚本,它用于对一个目录下的所有文件进行某种操作(包含子目录中的文件)。2、快排算法详见快排算法部分。 六、递归算法的时间复杂度计算方式1、代换法(1)猜测解的形式(2)用数学归纳法找出使解真正有效的常数 比如我们求解,递归式T(n) = 2T(n/2)+n,1-我们猜测解是O(nlgn),我们要寻找到一个常数c,使得T(n)<=cnlgn 2-证明:即T(n) <= 2c(n/2)lg(n/2)+n <= cnlgn-cnlg2+n = cnlgn-cn+n 只要c>=1,T(n)<=cnlgn,所以我们的猜测是正确的。2、递归树方法利用递归树方法求算法复杂度,其实是提供了一个好的猜测,简单而直观。在递归树中,每一个结点表示一个单一问题的代价,子问题对应某次递归函数调用。我们将树中每层中的代价求和,得到每层代价,然后将所有层的代价求和,得到所有层次的递归调用总代价。根据上式我们建立递归式T(n) = 3T(n / 4) + cn^2,建立下列递归树模型: 在递归树中,每一个结点都代表一个子代价,每层的代价是该层所有子代价的总和,总问题的代价就是所有层的代价总和。所以,我们利用递归树求解代价,只要知道每一层的代价和层数即可。 以上图为例,当递归调用到叶子T(1)时所用到的递归次数就是整棵递归树的深度。我们从图中可以得到第i层的结点的代价为n/(4^i),当n/(4^i)=1即i = log4(n)时,递归到达了叶子,故整棵递归树的深度为log4(n)。总代价是所有层代价的总和,结果为O(n^2)。计算过程详见算法导论。3、主方法 参考书目:1、数据结构与算法:C++语言版 作者:肖南峰,赵洁等2、数据结构与算法图解 作者:[美]杰伊·温格罗3、算法导论

May 31, 2020 · 1 min · jiezi

自定义值类型一定不要忘了重写Equals否则性能和空间双双堪忧

一:背景1. 讲故事曾今在项目中发现有同事自定义结构体的时候,居然没有重写Equals方法,比如下面这段代码: static void Main(string[] args) { var list = Enumerable.Range(0, 1000).Select(m => new Point(m, m)).ToList(); var item = list.FirstOrDefault(m => m.Equals(new Point(int.MaxValue, int.MaxValue))); Console.ReadLine(); } public struct Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } }这代码貌似也没啥什么问题,好像大家平时也是这么写,没关系,有没有问题,跑一下再用windbg看一下。 0:000> !dumpheap -statStatistics: MT Count TotalSize Class Name00007ff8826fba20 10 16592 ConsoleApp6.Point[]00007ff8e0055e70 6 35448 System.Object[]00007ff8826f5b50 2000 48000 ConsoleApp6.Point0:000> !dumpheap -mt 00007ff8826f5b50 Address MT Size0000020d00006fe0 00007ff8826f5b50 24 0:000> !do 0000020d00006fe0Name: ConsoleApp6.PointFields: MT Field Offset Type VT Attr Value Name00007ff8e00585a0 4000001 8 System.Int32 1 instance 0 x00007ff8e00585a0 4000002 c System.Int32 1 instance 0 y从上面的输出不知道你看出问题了没有? 托管堆上居然有2000个Point,而且还可以用 !do 打出来,说明这些都是引用类型。。。这些引用类型哪里来的? 看代码应该是 equals 比较时产生的,一次比较就有2个point被装箱放到托管堆上,这下惨了,,,而且大家应该知道引用对象本身还有(8+8) byte 自带开销,这在时间和空间上都是巨大的浪费呀。。。 ...

May 31, 2020 · 3 min · jiezi

006-C-自动生成信息卡

Hey,How are you doing? 2020年,对于我来说,既是机遇,也是挑战。 所谓挑战,是指C#实现办公自动化的系统课程几乎空白。 所谓机遇,是指做好了有可能成为C#办公自动化第一人。 我不是专业码农,没有太多项目经验。 庆幸的是,日常办公中接触到了很多高度重复的工作。 我的目的很明确,就是想通过C#实现重复工作自动化。 也许骨子里和很多人不同,我并没有追Python的风。 C/C++,大佬们通常用于操作系统、硬件底层等领域。 而Python主攻人工智能。 由于VBA存在,Python并不是最适合办公自动化的工具。 1995年Java诞生,2002年C#诞生, Java和C#同属C系语言,但C#比Java简洁易学。 如果你是想找工作,你可以学习Java。 咱们目的是实现重复工作自动化, 使用C#则可以实现更早下班。 我并不是特别厉害,曾研究过2个月VBA没有继续深造。 略懂一些基础语法,我感觉这样,其实就已经足够了。 通过短暂学习,我知道VBA是通过COM组件实现办公自动化。 其实,我也可以通过COM组件,用C#实现办公自动化。 就是抱着这样一种纯碎想法,我开了新坑[职场编码], 致力于贡献一些职场小白实现效率提升的小技巧。 说句实在话,我学了2个月VBA,就已经对Sub+End Sub, With+End With产生依赖了。 VBA实在太好用了,那时根本瞧不上C#。 2020年1月下旬,在家憋着没事,就开始研究C#,从基础语法,看到最新特性, 经过将近4个月的磨合,我发现, 自己竟然喜欢上了优雅的{花括号}和[索引]。 前者可以快速区分代码块,而不必使用游标卡尺为缩进而烦恼。 后者可以快速引用对象,而不必与对象的(方法)混为一谈。 前面,我所说的COM组件是指: Microsoft.Office.Interop.Word;Microsoft.Office.Interop.Excel;Microsoft.Office.Interop.Powerpoint;当然还有其他的组件,如Access、Publisher、Visio等。 因为日常接触到的基本就是办公三大件。 今后粉丝有需求,我会再开专题,详细讲解其他组件。 我们是技术文,排版其实不是最重要的。 通常推文以技术为主,若哪里看不懂, 或者我写得不够明确,欢迎私信联系。 下面,我们来看一下如何自动生成信息卡。 根据操作示意图,我们可以得知,只要把Excel数据写入Word模板里就可以了。 首先,我会在Word里建立一张信息卡模板,放在文件夹~c003\bin\Debug\。 接着,我用Sharp Develop创建一个控制台应用程序。 大致思路:观察一下,Excel共有10条数据准备写入, 那我就把Word模板复制9份,接着使用代码循环写入就可以了。 001 准备工作 日常引用Word\Excel, System.Runtime.InteropServices杀进程专用空间。 002 开始工作 常规操作:声明、可见、定义、打开Word\Excle。创建表格: 通过Mxr-2控制循环次数, Wdc.Tables.Count计算表格个数。数据写入: 通过Wdc.Tables[].Cell().Range.Text=Eap.Worksheets[].Cells().value.ToString()实现数据写入。003 扫尾工作 通过定义Kill方法,调用Kill方法实现Excel进程终结。 下面是源码贴图,如果你也想深入学习,回复 源码 获得源代码供你参考。 ...

May 30, 2020 · 1 min · jiezi

005-C-自动生成工资条

《喜欢你》影片中,顾胜男与路晋相杀相爱,顾胜男用一道道美食征服了路晋,最终走到了一起。 她曾说过,每个人身上都有密码,看我这把钥匙能否解开你的密码。Hey,How are you doing? 我是职场编码,很高兴认识你。 曾几何时,我觉得我和C#的关系,像极了顾胜男和路晋的坎坷情路。 结构严谨的C#,像极了毒舌的路晋,一有错误,就各种跳框叨叨我。 路晋为了品尝美食,一头扎进顾胜男家。 而我为了调试代码,一头埋进编码世界。 今天,我就给你演示一下C#自动生成工资条的实现过程。 001 准备工作 Excel初始化声明实例化Excel应用: Excel._ApplicationEap = newExcel.Application(); 设置Excel应用可见: Eap.Visible=true; 设置Excel路径: stringePth=AppDomain.CurrentDomain.BaseDirectory+"工资条.xls"; 打开Excel文件: Excel._WorkbookEbk = Eap.Workbooks.Open(ePth); Word初始化声明实例化Word应用: Word._ApplicationWap = newWord.Application(); 设置Word应用可见: Wap.Visible=true; 设置Word路径: stringPth=AppDomain.CurrentDomain.BaseDirectory+"工资条.doc"; 打开Word文件: Word._DocumentWdc = Wap.Documents.Open(Pth); 绘制表格,设置格式设置Word表格最大行为Excel有效行-1: intMxr = Eap.Worksheets[1].UsedRange.CurrentRegion.Rows.Count-1; 绘制表格: Wdc.Tables.Add(Wdc.Paragraphs[1].Range,Mxr*2,11); 表格实例化、加线、文字居中: Word.TableWtb = Wdc.Tables[1]; Wtb.Borders.InsideLineStyle=** Word.WdLineStyle.wdLineStyleSingle;** Wtb.Borders.OutsideLineStyle=** Word.WdLineStyle.wdLineStyleSingle;** Wtb** .Range.ParagraphFormat.Alignment= Word.WdParagraphAlignment.wdAlignParagraphCenter;** 002 开始工作 通过s%2==1控制,利用Wtb.Cell(s,j).Range.Text=Eap.Worksheets[1].Cells(1,j).value实现标题写入奇数行。 通过s=2,s+=2;i=2,i++控制,利用Eap.Worksheets[1].Cells(i,j).value.ToString()实现数据写入偶数行。 其中,.ToString("yyyy-MM")实现日期格式化。 其中,.ToString("0.00")实现数字格式化。 003 扫尾工作 利用Eapsht.Kill(Eap),调用Kill()方法,完成终结Excel进程。 ...

May 30, 2020 · 1 min · jiezi

使用PInvoke互操作让C和C愉快的交互优势互补

一:背景1. 讲故事如果你常翻看FCL的源码,你会发现这里面有不少方法借助了C/C++的力量让C#更快更强悍,如下所示: [DllImport("QCall", CharSet = CharSet.Unicode)] [SecurityCritical] [SuppressUnmanagedCodeSecurity] private static extern bool InternalUseRandomizedHashing(); [DllImport("mscoree.dll", EntryPoint = "ND_RU1")] [SuppressUnmanagedCodeSecurity] [SecurityCritical] public static extern byte ReadByte([In] [MarshalAs(UnmanagedType.AsAny)] object ptr, int ofs);联想到上一篇阿里短信netsdk也是全用C++实现,然后用C#做一层壳,两者相互打辅助彰显更强大的威力,还有很多做物联网的朋友对这种.Net互操作技术太熟悉不过了,很多硬件,视频设备驱动都是用C/C++实现,然后用winform/WPF去做管理界面,C++还是在大学里学过,好多年没接触了,为了练手这一篇用P/Invoke来将两者相互打通。 二:PInvoke互操作技术1. 一些前置基础这里我用vs2019创建C++的Console App,修改两个配置: 将程序导出为dll,修改成compile方式为Compile as C++ Code (/TP)。 2. 基本类型的互操作简单类型是最好处理的,基本上int,long,double都是一一对应的,这里我用C++实现了简单的Sum操作,画一个简图就是下面这样: 新建一个cpp文件和一个h头文件,如下代码。 --- Person.cppextern "C"{ _declspec(dllexport) int Sum(int a, int b);}--- Person.h#include "Person.h"#include "iostream"using namespace std;int Sum(int a, int b){ return a + b;}有一个注意的地方就是 extern "C",一定要用C方式导出,如果按照C++方式,Sum名称会被编译器自动修改,不信你把extern "C"去掉,我用ida打开给你看一下,被修改成了 ?Sum@@YAHHH@Z, 尴尬。 ...

May 29, 2020 · 2 min · jiezi

阿里短信回持net-sdk的bug导致生产服务cpu-100排查

一:背景1. 讲故事去年阿里聚石塔上的所有isv短信通道全部对接阿里通信,我们就做了对接改造,使用阿里提供的.net sdk。 网址:https://help.aliyun.com/docum... 同事当时使用的是ons-.net v1.1.3版本,程序上线后若干天就会有一次程序崩溃现象,当时也没特别在意,以为是自己代码或者环境出了什么问题,索性就加了一个检测程序,如果检测到sdk程序退出就自动重启,就这样先糊弄着,直到有一天服务器告警,那个程序CPU居然飙到100%,服务器可是16核128G的哦。。。 二:分析问题1. 抓dump文件情况比较紧急,马上给程序发送Ctrl+C命令让程序退出,结果又退出不了,奇葩。。。为了分析问题抓了一个dump下来,然后强制kill掉程序。 2. 查看线程池以及各个线程正在做什么?0:000> !tpCPU utilization: 100%Worker Thread: Total: 0 Running: 0 Idle: 0 MaxLimit: 32767 MinLimit: 16Work Request in Queue: 0--------------------------------------Number of Timers: 1--------------------------------------Completion Port Thread:Total: 1 Free: 1 MaxFree: 32 CurrentLimit: 1 MaxLimit: 1000 MinLimit: 16从 CPU utilization: 100% 上看,果然cpu100%了,发现 Worker Thread 没有Running 线程,可能是因为执行了Ctrl+C都销毁了,接下来用 ~*e !clrstack 把所有的托管线程栈打出来。 0:000> ~*e !clrstackOS Thread Id: 0x1818 (0)Unable to walk the managed stack. The current thread is likely not a managed thread. You can run !threads to get a list of managed threads inthe processFailed to start stack walk: 80070057从输出结果看,没有任何托管线程,唯一的那个线程0还不是还托管线程,然后改成 ~*e !dumpstack把非托管线程栈找出来。 ...

May 26, 2020 · 2 min · jiezi

NET-Core中使用OOM框架AutoMapper的使用介绍

(一)什么是OOM:OOM顾名思义,Object-Object-Mapping实体间相互转换,AutoMapper其意义在于帮助你无需手动的转换简单而又麻烦的实体间关系。 (二)AutoMapper是什么:AutoMapper是基于对象到对象约定的映射工具,常用于(但并不仅限制于)把复杂的对象模型转为DTO,一般用于ViewModel模式和跨 服务范畴。 (三)在.NET Core项目中如何使用它:1.通过Nuget安装AutoMapper到项目:Install-Package AutoMapper2.定义好Model类和DTO类:`//Model类public class ProjectEntity{ public int ID { get; set; }public string ProjectName { get; set; }public string ProjectImg { get; set; }public string ProjectCreateDate { get; set; }public List<ProjectTaskEntity> Tasks { get; set; }}` `//DTO类public class ProjectDto{ public int ProjectID { get; set; }public string ProjectName { get; set; }public string ProjectImg { get; set; }public string ProjectCreateDate { get; set; }public List<ProjectTaskDto> Tasks { get; set; }}` ...

November 4, 2019 · 1 min · jiezi

c-函数调用

前言这周我们学院做大实验,简单来说就是用c#语言来编写三个小应用,分别是简单计算器,学生成绩管理界面和超市选址问题,让我们去体会可视化编程的思想。再没有学过c#的情况下,我们靠着老师发的参考资料和上网查询,勉强按时完成了。这次主要来讲讲函数调用的问题。 需求如上所示“读取”是将某一指定文件下的数据展示到ListView框里(没错那个大白框叫ListVIew),剩下的功能分别是对文本进行编辑,所以每次点击剩下的功能后,都要再点一下读取ListView里的数据才能相应更改。本着能让用户点一下不让用户点两下的原则(其实是写出自动更新会加分),现在需要实现点击其他按钮后自动调用第一个按钮的方法。 同一窗口下实现是的,这很简单。拿删除举例,我们只要在删除函数最后调用读取函数就可以了。 函数既可以递归也可以嵌套调用,但是函数嵌套定义是不被允许的图中,我们在button4_Click函数最后调用了button1_Click函数,参数传入null。这里有一个问题,如果button1_Click函数后期用到传来的参数的时候,这样写就会出错。更好的方法是定义一个读取函数,然后所有的按钮函数都去调用这个读取函数。定义更新函数调用这个函数这样就解决了传入参数有误的问题 不同窗口下实现在这个程序中,我们不只有这一个窗口,还有新增/修改窗口和查询窗口拿新增/修改窗口举例,我们在Form2中直接调用Form1的函数肯定是不行的,我们需要将Form1传入到Form2中去原代码我们传入Form1与此同时我们在Form2中定义一个Form1 Form1 form1;接收Form1,传入的Form1是一个局部变量,我们用全局变量接收 当参数与变量名相同时,我们用this.form1表示变量名,用form1表示参数为了以后调用Form2()防止出错,我们再重载一下Form2函数 public Form2() { InitializeComponent(); }这样在调用读取函数时,加一个if语句。 if (this.form1 != null) { this.form1.reload(); }这样在Form2的确定按钮方法里,我们也可以调用Form1的读取函数了。 结语这个实现的过程里用了很多原来学过的c++知识,算是对原来的知识的复习吧,知识只有去应用他,我们才会记得更牢固,在此感谢学长的帮助。

November 2, 2019 · 1 min · jiezi

C-实现简单成绩管理系统

前言这周跟C#打了一周的交道(本周是学校安排的实验周,然后用到了C#实现想要的程序和功能)一共七个实验,选择三个,我就选择我进步最大的一个来分析一下吧。 效果先来看一下效果吧 从txt文本中读取数据后展示出来 点击目标后选中,然后点击“修改”,弹出修改界面,然后进行编辑即可 点击“统计”按钮,弹出窗口显示各分数段的信息 点击“查询”后,弹出界面,输入后,点击“确定”即可显示信息 实现一、准备工作在写方法之前,首先就是先把界面规划好,就是通过添加按钮和输入框或显示框组成一个符合要求的窗口或多个窗口 在这个程序中我们用到的主要是这几个组件 对文件进行操作要进行引用的声明,即“using”我们添加的是这两行 然后我们还要写一些代码来实现其他功能 public Form1() { InitializeComponent(); this.listView1.Columns.Add("学号", 100, HorizontalAlignment.Center); this.listView1.Columns.Add("姓名", 100, HorizontalAlignment.Center); this.listView1.Columns.Add("数学", 100, HorizontalAlignment.Center); this.listView1.Columns.Add("英语", 100, HorizontalAlignment.Center); this.listView1.Columns.Add("政治", 100, HorizontalAlignment.Center); this.listView1.Columns.Add("总分", 100, HorizontalAlignment.Center); this.listView1.Columns.Add("平均分", 100, HorizontalAlignment.Center); this.listView1.Columns.Add("名次", 100, HorizontalAlignment.Center); this.listView1.View = System.Windows.Forms.View.Details; this.listView1.FullRowSelect = true;//是否可以选择行 }“listview1”就是按钮上方实现显示的控件,“this”指的就是Form1这个窗口,“Columns”指的是“栏”,也就是上方的内容,“add”指的是把后面的内容作为“Columns”的内容,后面的“100”等都是“Columns”的属性,可以通过修改它的属性来修改它的大小和位置,还有一种生成“Column”的方法是通过属性栏来添加。点击listview一次选中它,然后右键单击一次,点击属性,会发现有“Column”这个属性,点进去后就可以进行编辑和修改了。 不得不说确实挺方便的,不过实验报告手册中给了部分必须的源码,再加上自己第一次接触C#,所以就没使用后面的方法,不过在后面的操作中使用了一下,确实挺爽。 二、读取操作这里的“读取”按钮读取的是统计之后的内容,并非成绩等信息,双击“读取”按钮后即可进行编辑(在按钮的属性中我修改了name属性为load,所以此处的方法名为“load_Click”) private void load_Click(object sender, EventArgs e) { this.load_data(); }此处调用了“load_data()”这个方法 public void load_data() { string file = File.ReadAllText("Score.txt", UTF8Encoding.Default); //把txt文件中按行存储的信息利用regex.Split按行存入string数组中 string[] records = Regex.Split(file, "\r\n"); //开始更新视图 this.listView1.BeginUpdate(); //清空原有视图 this.listView1.Items.Clear(); // records.Length为数组的元素个数 for (int index = 0; index < records.Length; index++) { //分割每行记录的各个字段 string[] components = Regex.Split(records[index], " "); //生成listview的一行 ListViewItem lvi = new ListViewItem(components); //添加背景色 lvi.SubItems[0].BackColor = Color.Red; //把新生成的行加入到listview中 this.listView1.Items.Add(lvi); } //视图更新结束 this.listView1.EndUpdate(); }这个方法就是以“/r/n”为分界线定义一个数组1,然后再以空格为分界线定义一个数组2,同时生成一个 ListViewItem 来显示数组2,然后再设置一下背景色,此处设置的为红色 ...

November 2, 2019 · 6 min · jiezi

SharpC-A-C-Interpreter-In-C-1000

函数的实现如下: public class FunctionDefine : Context{ private Stack<List<Expression.Operand.Value>> m_parameterStack; public DataTypeInfo ReturnType; public Expression.Operand.Operand ReturnValue; public bool IsVariableArgument; public int ReferenceCount; public int IteratorCount = 0; public List<Context> ArgumentDefinitions; public Block Body; ...其查找方法FindByName需要搜索参数列表: public override Context FindByName(string str){ if (ArgumentDefinitions != null) { foreach (FunctionArgumentDefine arg in ArgumentDefinitions) { if (arg.Name == str) return arg; } } return base.FindByName(str);}其运行方法实现如下: public virtual void Run(Context ctx, List<Expression.Operand.Operand> parameters){ Debug.WriteLine(string.Format("Call function \"{0}\" with [{1}] parameter{2}", Name, parameters.Count, parameters.Count > 0 ? "s" : "")); // 准备工作 BeforeRun(ctx, parameters); Run(ctx); // 清场工作 AfterRun(ctx); }准备工作包括: ...

October 15, 2019 · 5 min · jiezi

SharpC-A-C-Interpreter-In-C-0110

if勿庸质疑,说到控制结构,首先会想到if。其实现应如下所示: public class IfThen : ControlFlow{ public Expression.ExpressionNode Condition; public Block ThenClause { get { return Children.Count > 0 ? Children.First() as Block: null; } } public Block ElseClause { get { return Children.Count > 1 ? Children.Last() as Block: null; } }运行时处理则应该如下: public override void Run(Context ctx){ Debug.WriteLine("if(" + Condition.ToString() + ")"); Expression.Operand.Operand condVal = Condition.Evaluate(this); Debug.WriteLine("Condition=" + condVal.GetValue(this).ToString()); if (condVal.GetValue(this).AsInt != 0) { if (ThenClause != null) { Debug.WriteLine("Then"); ThenClause.Run(this); } } else { if (ElseClause != null) { Debug.WriteLine("Else"); ElseClause.Run(this); } }}看起来相当简单。 ...

October 15, 2019 · 4 min · jiezi

SharpC-A-C-Interpreter-In-C-0111

表达式,最简单也最困难。各种算术、逻辑、赋值埋同函数调用,想想都头大如斗转星移山填海枯石烂。废话有云,根据Yacc规则,表达式由操作数及操作符构成。操作数有立即数、变量、函数调用及另一个表达式。操作符有一元、二元及三元操作符。惜乎SharpC不支持三元表达式。有两类特殊的操作数:指针及指针指示(Pointer indiction应该怎么翻译?)。有一个特殊的操作符:sizeof。说其特殊是因为偶将其归类于一元操作符,且不像其它操作符般需要操作数,sizeof也可以类型名称作为操作数。先看看操作符优先级定义: public enum OperatorPriority{ // 赋值操作最低 Assign, Logic, Bitwise, // 算术运算有两级优先级:+,—是一级,*,/,%是高一级 ArthmeticLow, ArthmeticHigh, // 拔高了移位操作符优先级于算术之上 Shift, Address, Unary, Parenthese}先看看表达式求值过程: public Operand.Operand Evaluate(Context ctx){ if (Token is Operand.Operand) { if (Token is Operand.ValueOfFunctionCalling) { Operand.ValueOfFunctionCalling valOfFuncCalling = Token as Operand.ValueOfFunctionCalling; return valOfFuncCalling.GetValue(ctx); } else return Token as Operand.Operand; } else { List<Operand.Operand> operands = new List<Operand.Operand>(); if (LeftNode != null) operands.Add(LeftNode.Evaluate(ctx)); if (RightNode != null) operands.Add(RightNode.Evaluate(ctx)); Operand.Operand res = (Token as Operator.Operator).Evaluate(ctx, operands); Debug.WriteLine(string.Format("\tExp: [{0} {1} {2}] Result: [{3}]", operands.First().ToString(), Token.ToString(), operands.Count > 1 ? operands.Last().ToString() : "", res.ToString())); return res; }}表达式求值过程: ...

October 15, 2019 · 5 min · jiezi

SharpC-A-C-Interpreter-In-C-1100

偶有闲暇,添加了对结构的支持。也增加了对typedef structname synonym的支持。技巧: 结构成员在解析时转换成相对结构于的起始地址之偏移。在运行时计算成员的实际地址(运行时变量才会分配地址 )。成员按ValueOfPointerIndiction方式访问。示例代码: struct StructA { int a; int b; }; StructA theA; theA.a = 1; theA.b = 2; print("theA.a=%i\ntheA.b=%i", theA.a, theA.b); StructA *ptheA = &theA; ptheA->a = 3; ptheA->b = 4; print("theA.a=%i\ntheA.b=%i", theA.a, theA.b); print("ptheA->a=%i\nptheA->b=%i", ptheA->a, ptheA->b);结果输出:解析: SharpC.Grammar.Variable: Line: 192 Pos: 1 "theA;"SharpC.Grammar.Statement: Line: 194 Pos: 1 "theA.a = 1;"SharpC.Grammar.Statement: Line: 195 Pos: 1 "theA.b = 2;"SharpC.Grammar.Statement: Line: 197 Pos: 1 "print("theA.a=%i\ntheA.b=%i", theA.a, theA.b);"SharpC.Grammar.Variable: Line: 199 Pos: 1 "ptheA = &theA;"SharpC.Grammar.Statement: Line: 199 Pos: 1 "ptheA = &theA;"SharpC.Grammar.Statement: Line: 201 Pos: 1 "ptheA->a = 3;"SharpC.Grammar.Statement: Line: 202 Pos: 1 "ptheA->b = 4;"SharpC.Grammar.Statement: Line: 204 Pos: 1 "print("theA.a=%i\ntheA.b=%i", theA.a, theA.b);"SharpC.Grammar.Statement: Line: 205 Pos: 1 "print("ptheA->a=%i\nptheA->b=%i", ptheA->a, ptheA->b);"运行: ...

October 15, 2019 · 3 min · jiezi

SharpC-A-C-Interpreter-In-C-1011

现在,来看看成果。 以如下C代码为运行样本: int global_a = 1 + 2;int global_b = 1 - 2;int global_c = 1 * 2;int global_d = 1 / 2;int global_e = 1 % 2;int global_f = 1 & 2;int global_g = 1 | 2;int global_h = 1 ^ 2;int global_i = 1 && 2;int global_j = 1 == 2;int global_k = 1 > 2;int global_l = 1 >= 2;int global_m = 1 < 2;int global_n = 1 <= 2;int global_o = 1 != 2;int global_p = 1 || 2;int global_q = 1 << 2;int global_r = 1 >> 2;char global_ch = 'a';char* global_str = "abcdefghijklmn";void fork(){ int x = 1; if (x > 0) x = 2; else x = 3; // return ; x = 2; for(int i = 0; i < 5; i++) { x++; break; } while(x < 10) x++; x = 0; do { x++; }while(x < 5); x = 2; switch(x) { case 1: case 2: x++; case 3: break; case 4: default: break; } // switch in for for(x = 1; x < 10; x++) { switch(x) { case 1: break; case 2: case 3: { if (x == 2) x = 2; else x = 3; } break; case 4: { for(int y = 0; y < 5; y++) { if (y >= 3) break; } } break; case 6: break; case 7: case 8: default: { x = x; } break; } // switch } // for}void fork2(int a, int b){ print("iterate fork2: a=%i, b=%i\n", a, b); if (a + b == 2) return; else fork2(--a, --b);}void fork3(...){ int len = varlen(); print("Variable function:fork3\n"); print("length is %i\n", len); for(int i = 0; i < len; i++) { int x = vararg(i); print("x=%i\n", x); } return ;}int strlen(char* str){ int len = 0; char* ptr = str; while(*ptr != 0) { len++; ptr++; } return len;}void main(){ fork(); fork2(5, 5); fork3(1); fork3(1, 2); fork3(1, 2, 3); // Test pointer assign char* str = "abcdefg"; char* str2 = str; print("str=%s\n", str); print("str2=%s\n", str2); print("str len=%i\n", strlen(str)); print("str2 len=%i\n", strlen(str2)); char* str3 = malloc(5); *(str3 + 0) = 'a'; *(str3 + 1) = 'b'; *(str3 + 2) = 0; print("str3=%s\n", str3); int iVal = 0; if (input("Test Input", "Please input a string", "0", "%i", "Integer required", &iVal)) { print("result=%i \n", iVal); } switch(confirm("hello", "world")) { case 0: prompt("Result", "You pressed no."); break; case 1: prompt("Result", "You pressed yes."); break; }}运行之,则有:提示输入: ...

October 15, 2019 · 25 min · jiezi

SharpC-A-C-Interpreter-In-C-1010

表达式解析中比较重要的是表达式树中操作符的插入,需要比较优先级: private bool AddOperatorNode(ref Expression.ExpressionNode expTree, Expression.Operator.Operator op){ Expression.ExpressionNode node = new Expression.ExpressionNode() { Token = op }; if (expTree != null && expTree.Token is Expression.Operand.Operand) { node.LeftNode = expTree; expTree = node; } else { if (expTree == null) { expTree = node; } else { if (node.Token <= expTree.Token) { node.LeftNode = expTree; expTree = node; } else { Expression.ExpressionNode parent = null; Expression.ExpressionNode root = expTree; do { // Operand ? if (root.Token is Expression.Operand.Operand) { if (parent != null) { parent.RightNode = node; node.LeftNode = root; break; } else { node.LeftNode = root; root = node; break; } } // Less priority, new node will be add to right child node if (root.Token <= node.Token) { if (root.RightNode == null) { root.RightNode = node; break; } parent = root; root = root.RightNode; } else { // Higher priority if (parent == null) { node.LeftNode = root; expTree = node; } else { parent.RightNode = node; node.LeftNode = root; } break; } } while (true); } } } m_lastExpNode = node; return true;} // func接着是添加操作数的操作,需要对类型匹配进行验证: ...

October 15, 2019 · 7 min · jiezi

SharpC-A-C-Interpreter-In-C-1001

在了解词法分析之前,先看看对单词的定义: /// <summary>/// A structure for the result of word parsing./// </summary>public class Word{ public int AbsoluteLine; public int AbsoluteStartPos; public int AbsoluteEndPos; public int Offset; public int Column; public string Text; public bool IsAvailable; public Context.LocationInfo Location; }注意为了方便计,实现中并未将所有词语都标记为单词,比如操作符。仅有关键字、变量名和函数名被标记为单词。为了更好的分析源代码,创建了一个SourceCode类,以进行词法分析工作: public class SourceCode{ //各种标记 public static char[] MultiLineCommentsStartMark = new char[] { '/', '*' }; public static char[] HexMark = new char[] { 'x', 'X' }; public static char[] ExponentialMark = new char[] { 'e', 'E' }; public static char[] FloatMark = new char[] { 'f', 'F' }; public static char[] PlusMinusMark = new char[] { '-', '+' }; public string Text = ""; public int Line = 0; public int ColumnOfCurrentLine = 0; public int PosOffset = 0; public int LineOffset = 0; public int Column; public Word LastWord; public SourceCode() { } public SourceCode(string txt); public void LoadFromFile(string path); public bool Eof; // 当前位置在整个代码中的绝对位置 public int AbsolutePos; // 当前行在整个代码中的绝对行 public int AbsoluteLine; public Context.LocationInfo Location; public char CurrentChar; // 重置位置索引(局部位置索引) public void ResetPos(); // 检测下一个字符是否与指定的字符匹配 public bool TestNextChar(char c); // 检测下一个字符是否在指定的字符集中 public bool TestNextChar(char[] chars); // 获取下一个字符,默认会跳过前面的空格 public void NextChar(bool skipSpace = true); // 获取从当前位置开始的剩余代码 public string Tail; public static bool IsDigit(char ch); public static bool IsLetter(char ch); public static bool IsSpace(char ch); public static bool IsOperator(char ch); public static bool IsBracket(char ch); public void SkipSpace(); // 以';'为分隔符,划分代码片断 public List<SourceCode> SplitStatement(); // 以','为分隔符,划分多个变量定义片断 public List<SourceCode> SplitMultiDeclaration(); // 划分参数 public List<SourceCode> SplitParameter(); // 获取一个单词 public static Word GetWord(SourceCode src); // 获取括号内的代码 public static SourceCode GetBracketCode(char leftBracket, char rightBracket, SourceCode src);}更复杂的分析,与语法分析一道,整合到Parser类中。Parser类用于对C代码进行语法分析并构造语法树。 ...

October 15, 2019 · 10 min · jiezi

程序员是不是青春饭

有人问到程序员是不是吃"青春饭",回答是肯定的,没有哪个行业可以像互联网一样,不需要背景、不需要关系,只需要才华就有舞台。看看国内国外那些知名大佬如李彦宏、马化腾、张小龙、雷军、张一鸣、小扎基本都是30岁前后完成了人生的小巅峰。 但是他们都没有因为年龄退出舞台反而都成某个领域的头部大佬,如果你30出头还要和毕业不久的人抢饭碗,不仅没优势,年龄反而限制了你,企业为啥不花更少的钱找一个可塑性更高的人呢? 最近在做些面试发现不少毕业三五年的,你找不出什么亮点出来,你问他算法他说忘记了,你问他一些基础计算机知识他说大部分时间都在写业务代码,你问他平时遇到过哪些挑战性的问题,他跟你说一般百度解决了。这类人吃的就真是青春饭。 那是不是到了35岁程序员真的就只能转行了呢?答案是否定的。 互联网行业给了年轻人机会,特别是程序员,优秀的人才30岁靠自身能力可以实现自由,如果换做是医生、教师行业你做梦都不敢想,你只能熬,熬到四五十岁才有上位机会,二十多岁的你愿意熬吗?而互联网科技行业,不说马化腾、雷军那一代程序员,就拿当下来说,华为给应届毕业生(博士)是200万的年薪,你觉得他们吃的是青春饭吗?肯定不是啦,他们靠的是自身的能力而不是年龄。 所以30岁前你就要去规划自己的未来, 你可以把自己规划为某个领域稀缺人才,或者是综合能力全面提升,做个能打仗又能带队的管理者。另一条出路就是给自己写代码,如果你有良好的商业思维,善于发现生活中的痛点并且能够用技术去解决的话,你可以给自己写一辈子代码。 别等到35的岁的时候等着公司把你裁掉更多写作与参考学习材料等可登录ZG文库网http://www.zgwenku.com/

October 6, 2019 · 1 min · jiezi

C-的-构建工具

构建工具: 简单的构建工具:类似 C++ 的 makefile复杂的构建工具: .csproj可能涉及的命令: msbuildcscgcsdotnet路径 (mac): /usr/local/share/dotnet/dotnet/Library/Frameworks/Mono.framework/Versions/6.4.0/Commands

October 2, 2019 · 1 min · jiezi

设计模式6适配器模式

适配器模式适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。我们通过下面的实例来演示适配器模式的使用。其中,音频播放器设备只能播放 mp3 文件,通过使用一个更高级的音频播放器来播放 vlc 和 mp4 文件。 介绍意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)如何解决:继承或依赖(推荐)。关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。应用实例: 1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3、在 LINUX 上运行 WINDOWS 程序。 4、JAVA 中的 jdbc。优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。 实现我们有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。我们还有另一个接口 AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter,并使用 AdvancedMediaPlayer 对象来播放所需的格式。AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,不需要知道能播放所需格式音频的实际类。AdapterPatternDemo,我们的演示类使用 AudioPlayer 类来播放各种格式。 ...

September 10, 2019 · 1 min · jiezi

足球数据API接口-足球赛事分析数据API调用示例代码

分享下足球赛事分析数据api接口的示例代码,详细可查看接口文档,需注册下 package com.huaying.demo.football; import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Paths;import java.util.Arrays;import java.util.List; /** * 43.足球赛事分析数据 * * @Website: https://www.feijing88.com */public class FootballMatchStatistics { public static void main(String[] args) { try { String content = getContent(); Statistics statistics = Statistics.parseFrom(content); System.out.println(statistics); } catch (Throwable t) { t.printStackTrace(); } } /** * 获取API返回内容 * <p> * Note: 这里为了方便测试我使用了一份本地文件,使用时应替换为真实接口返回内容 */ private static String getContent() { try { StringBuilder builder = new StringBuilder(); List<String> lines = Files.readAllLines(Paths.get("./src/main/resources/FootballMatchStatistics.txt"), StandardCharsets.UTF_8); lines.forEach(line -> { builder.append(line); builder.append("|"); }); return builder.toString(); } catch (Throwable t) { t.printStackTrace(); return ""; } } public static class Statistics { private List<String> vsRecord; private List<String> homeRecentRecord; private List<String> awayRecentRecord; private List<String> homeFuture; private List<String> awayFuture; private List<String> homeOddsAll; private List<String> homeOddsHalf; private List<String> awayOddsAll; private List<String> awayOddsHalf; private List<String> homeGoalsDispersion; private List<String> awayGoalsDispersion; private List<String> homeStatistics; private List<String> awayStatistics; private List<String> homeGoalsTime; private List<String> awayGoalsTime; public static Statistics parseFrom(String data) { Statistics statistics = new Statistics(); statistics.parse(data); return statistics; } private void parse(String date) { String[] values = date.split("\\$\\|"); int i = 0; vsRecord = Arrays.asList(values[i++].split("\\|")); homeRecentRecord = Arrays.asList(values[i++].split("\\|")); awayRecentRecord = Arrays.asList(values[i++].split("\\|")); homeFuture = Arrays.asList(values[i++].split("\\|")); awayFuture = Arrays.asList(values[i++].split("\\|")); homeOddsAll = Arrays.asList(values[i++].split("\\|")); homeOddsHalf = Arrays.asList(values[i++].split("\\|")); awayOddsAll = Arrays.asList(values[i++].split("\\|")); awayOddsHalf = Arrays.asList(values[i++].split("\\|")); homeGoalsDispersion = Arrays.asList(values[i++].split("\\|")); awayGoalsDispersion = Arrays.asList(values[i++].split("\\|")); homeStatistics = Arrays.asList(values[i++].split("\\|")); awayStatistics = Arrays.asList(values[i++].split("\\|")); homeGoalsTime = Arrays.asList(values[i++].split("\\|")); awayGoalsTime = Arrays.asList(values[i].split("\\|")); } @Override public String toString() { return "Statistics{" + "\nvsRecord=" + vsRecord + ", \nhomeRecentRecord=" + homeRecentRecord + ", \nawayRecentRecord=" + awayRecentRecord + ", \nhomeFuture=" + homeFuture + ", \nawayFuture=" + awayFuture + ", \nhomeOddsAll=" + homeOddsAll + ", \nhomeOddsHalf=" + homeOddsHalf + ", \nawayOddsAll=" + awayOddsAll + ", \nawayOddsHalf=" + awayOddsHalf + ", \nhomeGoalsDispersion=" + homeGoalsDispersion + ", \nawayGoalsDispersion=" + awayGoalsDispersion + ", \nhomeStatistics=" + homeStatistics + ", \nawayStatistics=" + awayStatistics + ", \nhomeGoalsTime=" + homeGoalsTime + ", \nawayGoalsTime=" + awayGoalsTime + "\n}"; } }}API 返回数据如下(部分): ...

September 10, 2019 · 3 min · jiezi

设计模式4建造者模式

建造者模式建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。 介绍意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。何时使用:一些基本部件不会变,而其组合经常变化的时候。如何解决:将变与不变分离开。关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。应用实例: 1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。 2、JAVA 中的 StringBuilder。优点: 1、建造者独立,易扩展。 2、便于控制细节风险。缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。 实现我们假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。我们将创建一个表示食物条目(比如汉堡和冷饮)的 Item 接口和实现 Item 接口的实体类,以及一个表示食物包装的 Packing 接口和实现 Packing 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。然后我们创建一个 Meal 类,带有 Item 的 ArrayList 和一个通过结合 Item 来创建不同类型的 Meal 对象的 MealBuilder。BuilderPatternDemo,我们的演示类使用 MealBuilder 来创建一个 Meal。 具体代码实现参见https://github.com/Hp1512/Lea...

September 10, 2019 · 1 min · jiezi

NetC常用系统操作获取系统文件设置开机启动等

获取常用系统文件目录static void Main(string[] args){ //1、通过Environment.GetFolderPath()获取 string pathFavorites = Environment.GetFolderPath(Environment.SpecialFolder.Favorites);//获取我的收藏路径 Console.WriteLine("Favorites:" + pathFavorites); string pathDesktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);//获取桌面路径 Console.WriteLine("Desktop:" + pathDesktop); string pathMyDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);//获取我的文档路径 Console.WriteLine("MyDocuments:" + pathMyDocuments); string pathMyPictures = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);//获取我的图片路径 Console.WriteLine("MyPictures:" + pathMyPictures); string pathMyVideos = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos);//获取我的视频路径 Console.WriteLine("MyVideos:" + pathMyVideos); string pathDownloads = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\Downloads";//获取下载路径(这个没有找到,所以需要配合UserProfile来拼接了) Console.WriteLine("Downloads:" + pathDownloads); //2、通过环境变量获取 string tempPath = Environment.GetEnvironmentVariable("TEMP"); Console.WriteLine("TEMP:" + tempPath); Console.ReadKey();}设置开机启动private string registryName = "BootTestDemo";//启动项的名称private void btnSubmit_Click(object sender, EventArgs e){ if (this.ckBootEntry.Checked) { string filePath = Environment.CurrentDirectory + "\\PowerBootDemo.exe"; SetBoot(this.registryName, filePath); } else { DeleteBoot(this.registryName); } MessageBox.Show("Success");}/// <summary>/// 检测注册项是否存在/// </summary>/// <param name="key"></param>/// <returns></returns>private bool CheckExisting(string key){ string[] allNames = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run").GetValueNames(); foreach (var item in allNames) { if (item.Equals(key)) { return true; } } return false;}/// <summary>/// 设置启动项/// </summary>private void SetBoot(string key, string path){ if (CheckExisting(this.registryName)) return;//检测是否存在 Microsoft.Win32.RegistryKey registryKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run");//在系统启动的注册表中创建子项 registryKey.SetValue(key, path);//为子项进行赋值}/// <summary>/// 删除启动项/// </summary>public void DeleteBoot(string key){ if (!CheckExisting(this.registryName)) return;//检测是否存在 Microsoft.Win32.RegistryKey registryKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run"); registryKey.DeleteValue(key, false);}//PS:如果记不住这么长的路径可以直接打开系统注册表编辑器来翻看(cmd执行:regedit)。持续更新.... ...

September 10, 2019 · 1 min · jiezi

电竞CSGO数据API接口-即时指数API调用示例代码

CSGO的【即时指数】api接口调用示例,在线文档查看,需注册下 mport com.alibaba.fastjson.JSON;import com.alibaba.fastjson.annotation.JSONField; import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Paths;import java.util.List; /** * @API: 赔率信息 * @Website: https://www.feijing88.com */public class CsgoBet { public static void main(String[] args) { try { String content = getContent(); Respond rsp = JSON.parseObject(content, Respond.class); System.out.println(rsp.code); System.out.println(rsp.message); rsp.getData().forEach(System.out::println); } catch (Throwable t) { t.printStackTrace(); } } /** * 获取API返回内容 * <p> * Note: 这里为了方便测试我使用了一份本地文件,使用时应替换为真实接口返回内容 */ private static String getContent() { try { StringBuilder builder = new StringBuilder(); List<String> lines = Files.readAllLines(Paths.get("./src/main/resources/CsgoBet.json"), StandardCharsets.UTF_8); lines.forEach(builder::append); return builder.toString(); } catch (Throwable t) { t.printStackTrace(); return ""; } } public static class Respond { @JSONField private int code; @JSONField private String message; @JSONField private List<Bet> data; public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public List<Bet> getData() { return data; } public void setData(List<Bet> data) { this.data = data; } } public static class Bet { @JSONField private String betId; @JSONField private String matchId; @JSONField private String title; @JSONField private long endTime; @JSONField private int status; @JSONField private String resultId; @JSONField private List<Options> options; @JSONField private int betType; @JSONField private int boardNum; @JSONField private String typeDesc; @Override public String toString() { return "Bet{" + "betId='" + betId + '\'' + ", matchId='" + matchId + '\'' + ", title='" + title + '\'' + ", endTime=" + endTime + ", status=" + status + ", resultId='" + resultId + '\'' + ", options=" + options + ", betType=" + betType + ", boardNum=" + boardNum + ", typeDesc='" + typeDesc + '\'' + '}'; } public void setBetId(String betId) { this.betId = betId; } public void setMatchId(String matchId) { this.matchId = matchId; } public void setTitle(String title) { this.title = title; } public void setEndTime(long endTime) { this.endTime = endTime; } public void setStatus(int status) { this.status = status; } public void setResultId(String resultId) { this.resultId = resultId; } public void setOptions(List<Options> options) { this.options = options; } public void setBetType(int betType) { this.betType = betType; } public void setBoardNum(int boardNum) { this.boardNum = boardNum; } public void setTypeDesc(String typeDesc) { this.typeDesc = typeDesc; } } public static class Options { @JSONField private String betItemId; @JSONField private String teamId; @JSONField private String name; @JSONField private String odds; @Override public String toString() { return "Options{" + "betItemId='" + betItemId + '\'' + ", teamId='" + teamId + '\'' + ", name='" + name + '\'' + ", odds='" + odds + '\'' + '}'; } public void setBetItemId(String betItemId) { this.betItemId = betItemId; } public void setTeamId(String teamId) { this.teamId = teamId; } public void setName(String name) { this.name = name; } public void setOdds(String odds) { this.odds = odds; } }}API 返回数据如下(部分): ...

August 28, 2019 · 3 min · jiezi

程序员过关斩将来自于静态方法和实例方法的联想翩翩

这两周没有妹子来找我问问题,有点小伤感,所以耽误更新了。哈哈,别当真,因为菜菜这两周周末都有事(你可以认为去公司加班了),实在是没有精力,忘各位见谅!!以下为菜菜自己观点,不代表任何妹子的观点,请轻喷面向对象作为一个久经考验并得到业界肯定的编程思想莫过于面向对象编程思想了。 面向对象(Object Oriented,OO)是软件开发方法。面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。谈到面向对象思想,首先你得有一个对象才可以。所以计算机天才在语言角度发挥抽象能力,在编程中把对象抽象创建了出来,典型的代表作就是java/c# 中的类(class)。把每一个class的类型看做现实世界中的一类对象,然后根据class 可以创建出来多个class的实例,把这些实例看做是面向的具体对象。 为了完美的支持面向对象,大多数语言都支持了特性:封装,继承,多态。这也是诸多蛋疼的面试题中的常见题型。应用场景引入实例化方法概念是面向对象概念出现以后的事情了,区分静态方法和实例化方法不能单单从性能上去理解,创建c++,java,c#这样面向对象语言的大师引入实例化方法一定不是要解决什么性能、内存的问题,而是为了让开发更加模式化、面向对象化。这样说的话,静态方法和实例化方式的区分是为了解决模式的问题。 说的白话一点,到底是使用实例方法还是静态方法取决于业务的场景,当你的业务中每个对象都有自己的状态,或者行为,这些状态和行为是只属于当前对象的,那你的行为可以设计成实例方法。举一个很简单的例子:一个游戏的项目中,每个玩家(player)都有自己的状态,比如玩家有一个行为:跳跃,不同的玩家跳的距离可能不同,所以这个跳跃的行为体现到代码上就是一个player类型实例的方法。 至于静态方法,一般的定义成类型的行为和状态。因为类型是所有实例共享的,所以通常用作全局共享用途。实际项目中会发现有很多的helper类里边都是静态方法,因为这些方法和具体对象,和具体对象的行为状态没有任何关系。因为和具体实例没有连接,所以这类型的静态方法几乎都是线程安全的。举个很简单的例子:项目中有很多加密的方法,这些方法的作用就是给一个参数,返回一个结果,没有任何自己的状态,所以这些方法被设计成静态方法。 在多数项目中,实例方法的使用量要大于静态方法,为什么呢?因为在多数系统中充斥着各种对象的设计,各种XX设计模式的使用,而这些最终都使用了面向对象的思想。举一个最简单的mvc例子,无论是java中还是c#的 mvc框架,controller中的方法都是实例方法,因为每个http请求都有自己的状态,像header头信息,body信息等,这些状态是属于当前http请求的,所以这些controller必须是实例方法才行。 几乎现代所有的流行编程语言都提供了类型实例的继承和多态,统统都是为了更好的服务面向对象这个理念。为什么不提供类型的继承和多态呢?小伙伴们可以留言! 常见问题静态方法是类型的方法,实例方法是每个实例的方法(每个语言形式不太一样): class Bird { //静态方法 static bool IsAnimal() { return true; } //实例方法 bool IsCanFly() { return true; } }静态方法比实例方法快?菜菜认为这是错误的。一个方法的代码被加载到内存中,然后被cpu去执行,执行的速度快慢和是不是静态方法没有任何关系。但是有一个特殊的场景,那就是GC。实例化太多对象在java/c#这类带有GC的编程语言中会引发垃圾回收操作,当垃圾回收进行的时候会挂起所有的线程,所以在这个短暂的时间里,程序会卡顿。 静态方法常驻内存?在一个类型第一次被使用的时候,会把静态方法和静态变量载入内存,直到进程被销毁。说道常驻内存,也算是一种误解,正确的说法是只有在被使用之后才会加载进入内存。当然在一些语言中可以手动卸载当前类型。 静态方法没有线程安全问题菜菜认为是错的。有没有线程安全问题不是是不是静态所决定的,一个类型也可以有自己的状态和行为,只不过在一个进程中只有一份而已。当一个类型中的状态被多个线程修改的时候,就会有资源竞争问题,就会有线程安全问题。当一个类型的状态只有读的情况下,可以认为读这个方法是线程安全的。 自己运行一下以下程序的结果 class Program { static void Main(string[] args) { for (int i = 0; i < 20; i++) { Thread t = new Thread(() => { for (int i2 = 0; i2 < 100000; i2++) { Add(); } }); t.Start(); } //为了模拟程序一直运行 while (true) { Console.WriteLine($"Num的值:"+Num); Thread.Sleep(1000); } Console.Read(); } public static int Num; public static void Add() { Num= Num+1; } }至于实例方法的线程安全问题,原理类似。有没有线程安全问题取决于状态有没有被多个线程并发修改,有没有资源竞争,和是否静态完全没关系。 ...

July 15, 2019 · 1 min · jiezi

程序员过关斩将小小的分页引发的加班血案

问题分析通过以上的对话,身为程序员的你是否也遇到过妹子这样的问题呢?传统的而且网上到处充斥着的也是这类方式,客户端根据自己的滚动不断的更新pagesize和pageindex两个参数,然后上传给服务端接口获取数据,而且网络上也很少说明这种方式是否有问题,那到底有没有问题呢? 谈到分页,无论程序怎样写,分页这个业务的核心动作是根据开始位置和结束位置来获取一段数据,无论你的排序规则有多复杂,最终的目的总是获取总列表数据中一段连续的数据。无论你是直接用的sql语句分页,还用的搜索引擎(比如es),最终在客户端体现的效果就是下一页的数据展现。 当然体现在客户端的UI上的交互操作可以有很多样式 如果是瀑布流或者app段滚动展示的方式,或者其他不需要数据总个数的情况下,菜菜认为服务端千万不要查询这个总个数数据,展示方完全可以以下一页有无数据作为是否继续拉取下一页数据的依据。话题回归,如果客户端依据pagesize和pageindex参数来进行分页需求,有没有问题呢?当然有,要不然菜菜写这篇文章意义何在,我又不是一个喜欢爱扯淡的程序员~~ 问题所在这里以最简单也是最基本的sql 语句分页为例,假如现在数据库现有数据为 1,2,3,4,5,6,7排序的规则是按照大小倒序,即数据的全部列表为: 7,6,5,4,3,2,1假如现在是获取第二页数据,pagesize为2,pageindex为2,正确结果为 “5,4” 。这无可厚非,在数据未发生改变的情况下,正确结果确实如此,那如果数据发生的变化呢,假如现在新加入一条数据 8,列表数据会变为 8,7,6,5,4,3,2,1那依据以上分页原则,第二页获取的数据就变为了“6,5”,聪明的你是不是发现了问题,这也可能是D妹子引发加班的原因。 分页的操作是建立在动态数据上的操作解决问题分页操作的数据源是动态变动的,有时候变动的部分正好发生在你获取的数据范围内,就会发生数据重复或者错误的情况。那怎么解决呢? 客户端作为数据的需求方和展示方,客户端需要记住已经加载的数据的主键列表,如果某条数据已经展示过,根据业务需求来确定是否要重复展示,一般情况下需要去重。 如果数据量非常大,客户端维护一个数据池的方案其实也不够理想服务端服务端分页接口参数新增上一页最后一条数据id参数lastId,去掉pageindex参数,因为在多数情况下,pageindex参数在服务端的作用是确定数据的起点而已,如果有了lastid,pageinde在很多情况下其实已经不需要了。服务端把所有的数据做缓存,这样动态数据在一定时间内静态化,但是这样也是治标不治本。如果业务上对于排序无要求的话,服务端可以采用顺序分页,把获取的数据落在不会变动的数据段上服务端要想把动态的数据搞成静态有点难度业务方无论程序怎么优化也改变不了数据是在不停变动的本质,如果业务方(产品,运营)能够接受数据在偶尔情况下能重复的现象,那能大幅度减少程序员的工作了。 有时候你认为的数据bug,在其他业务部门不一定是什么重大问题添加关注公众号:架构师修行之路,获取更多精彩内容

July 15, 2019 · 1 min · jiezi

热度不如Java网友呛声还有C不能做的事

C#是微软公司发布的一种面向对象的、运行于.NET Framework之上的高级程序设计语言,自发布以来受到了程序员的广泛关注。C#与Java有着很多相似之处,例如,单一继承、接口、与Java几乎同样的语法和编译成中间代码再运行的过程。 但是与Java每月被唱衰的热度不同,C#看起来就沉默多了。根据TIOBE编程语言指数来看,从2016年开始,C#的热度就比较平稳,只是每年都会出现周期性的变化。如果从历史排名来看,C#一直是以小步伐在慢慢向前进的。 为什么C#的媒体曝光度不大,但却可以在众多编程语言中保持比较强的竞争优势呢?我想这与C#广泛的应用范围不无关系。那么,C#到底可以做什么呢? 首先,我们先整体来看一下C#的全能功夫单,在桌面端C#有WPF、WinForm、UWP等框架,在服务器端有 ASP. Net Core, ASP . Net Mvc ,WebForm,WCF,在Android、iOS开发端有Xamarin,在游戏开发方面有Unity,在IOT方面还有. Net Core。 据悉,Bing和Azure也有很多是C#写的,大多数金融公司的系统都是使用C#,甚至有人还使用C#写了操作系统。 适用于Windows下的一切 C#的优势很大一部分都来自于Windows,好的C#程序员可以使用C#将Windows的各种性能都挖掘出来,包括各种组件的调用。很少有语言将Windows核心库中的各种组件添加到项目并引用,但是C#依靠强大的Visual Studio就可以做到这一点。 .Net Core将会迎来大爆发 宝宝起名网 近期,在很多地方都看到有技术人在称:2018年,.Net Core将全面爆发,我们也看到了官方在大力推行.Net Core,2.0版本几乎可以完全符合程序员的需求,相当于.net framework 4.6.1的程度,原本.net framework的类库也几乎全部重写了,性能提升了一大截。 .Net Core可以跑在Linux、MacOS、Windows上,有网友称,他们利用.Net Core在树莓派上进行无人机的通信和飞控应用的开发,在Windows上把程序编译好,可以直接把编译结果传到树莓派的Ubuntu上面运行,而Ubuntu根本连sdk和runtime都不用安装。 C#在游戏领域一骑绝尘 C# + Unity已经快成为了游戏领域的经典搭配,再加上.Net Core服务器的加持,C#的开发体验在一众编程语言中脱颖而出。C#在游戏开发领域本来就有很好的使用历史,再加上性能不俗,在游戏领域一骑绝尘也是意料之后。 网友一句话评价C# 如何用一句话来评价C#的使用范围呢?我们来看看网友都有哪些神评论吧! 网友1:可以问个问题吗?有哪些使用C#不能完成的工作? 网友2:C#:我不是说你XXX,而是在座的各位,在windows服务器作为基础的情况下,你们都是垃圾。 网友3:这么说吧,我们公司正在用.net core写区块链…… 网友4:即使你用其它语言来做开发,C#也会给你带来神助攻的感觉!

July 13, 2019 · 1 min · jiezi

强类型弱类型静态语言动态语言的区别

弱类型: "1"+2'12'强类型: "1"+2会报错静态类型: public void ShowHi(){ int a = "Hi!" string b = a; }以上是c#的代码,静态类型语言在编译时遇到trap错误就会立即提醒。 动态类型: def ShowHi(): a = 'Hi!' - 1ShowHi()以上为python代码,而动态语言在执行时遇到trap错误才会提醒。 备注:trap意思是陷阱,也被称为异常或故障。

July 12, 2019 · 1 min · jiezi

AElf随机数合约标准ACS6

本文主要讨论区块链系统中随机数的常见方案,AElf中对于可提供随机数的智能合约提供的标准接口,以及AEDPoS合约对ACS6的实现。 关于ACS的说明可见这篇文章的开头。 区块链和随机数区块链系统中,与合约相关的随机数应用大致有几种场景:抽奖、验证码、密码相关等。 而由于区块链本质上是一个分布式系统,他要求各个节点的运算结果是可验证的,传统的随机数生成结果在不同机器上基本不会一致,让所有的节点产生同样的随机数又不造成过多的延时是不可能的。 好在区块链系统中生成一个可用的随机数,我们已知有几种方案。 中心化生成随机数。随机数由可信的第三方提供,如RANDOM.ORG。Commitment方案,或者hash-commit-reveal方案。如果读者有读过AElf白皮书会发现AElf主链共识用于确定每一轮区块生产者确定生产顺序的in_value和out_value便采用了这种方案:区块生产者在本地生成一个随机的哈希值in_value后,先公布承诺(即out_value),其中out_value = hash(in_value),到了合适的时机再公布随机哈希值in_value,其他节点只需要验证out_value == hash(in_value)就可以了。这里的in_value可以认为是一个随机数。采集区块链状态信息作为种子,在合约中生成随机数。万一被人知道了随机数的生成算法(智能合约的代码是公开的),再获取到正确的种子,这个方案生成的随机数就可以成功预测的。不敢相信还真有人用这种方式。 显然,站在去中心化的角度上考量,Commitment方案至少是一个可用的方案,只需要保证作出承诺(commitment)的人不会自己偷偷提前公开随机数,或者自己利用随机数作弊即可。 然而很不幸,其实在区块链系统中,这是无法保证的:我们无法保证生成随机数的人不会利用信息不对等来做出不公平的事情,比如当这个随机数被作为某次赌局开奖的依据时,随机数的生成者哪怕在赌局开始之前就做出了承诺,依然可以选择性地中止公开这个随机数——这样相当于他得到了“再玩一次”的机会,因为如果他不公开这个随机数,要么赌局会选择其他人公开的随机数,要么这个赌局会作废。 如果预防随机数生产者的选择中止攻击呢?有一系列成熟的方案,参看Secret Sharing。 简单解释一下:现在有五个人A~E,每人掌握一个公私钥对,此时A产生了一个随机数Random,生成对应的承诺Commitment,同时将随机数Random与B、C、D、E的公钥进行加密得到四个SharingPart,加密SharingPart时便保证只需要凑够B~E中两个人就可以恢复Random,将SharingPart和Commitment一起公开。这样哪怕他自己因故没有公开Random的值,B~E中任意两个人用自己的私钥分别对自己收到的SharingPart解密,凑齐两个解密后的数值(要按A加密出SharingPart的顺序),便可以恢复出Random。而万一两个SharingPart没能恢复出Random,只能认为A从一开始就决定作恶——这时候只需要在区块链经济系统的设计中,让A付出代价就行了。比如直接扣除A申请称为随机数生产者缴纳的保证金。(TODO: 画图) 此外,我们还可以选择不依赖某一个个体产生的随机数,而是选择多个个体的随机数进一步计算哈希值作为应用场景中的可用随机数。如此我们可以比较稳定、安全地在区块链系统中得到随机数。 ACS6 - Random Number Provider Contract Standard我在之前的一篇文章中解释了AElf区块链关于共识的合约标准,实际上是作为AElf主链开发者对合约开发者实现共识机制时推荐实现的接口。而关于随机数生成,我们制定了ACS6,作为对任何提供随机数的合约推荐要实现的接口。 不出意外地,ACS6是选择对Commitment方案进行抽象,得到的合约标准。 支持使用ACS6的场景如下: 用户对实现了ACS6的合约申请一个随机数,类似于发送了一个定单;实现了ACS6的合约给用户返回一些信息,这些信息包括用户可以在哪个区块高度(H)获取得到一个随机数,以及用户获取随机数可用的凭据T(也是一个哈希值);等待区块链高度到达指定高度H后,用户发送交易尝试获取随机数,凭据T需要作为该交易的参数;实现了ACS6的合约根据凭据T返回一个随机数。如果用户尝试在高度H之前获取随机数,本次获取随机数的交易会执行失败并抛出一个AssertionException,提示高度还没到。 基于以上场景,我们设计的ACS6如下: service RandomNumberProviderContract { rpc RequestRandomNumber (google.protobuf.Empty) returns (RandomNumberOrder) {}rpc GetRandomNumber (aelf.Hash) returns (aelf.Hash) {}} message RandomNumberOrder { sint64 block_height = 1;// Orderer can get a random number after this height.aelf.Hash token_hash = 2;}用户发送RequestRandomNumber交易来申请一个随机数,合约需要为本次请求生成一个凭据(token_hash),然后把该凭据和用户能够获取该随机数的区块高度一起返回给用户。高度达到以后,用户利用收到的凭据(token_hash)发送GetRandomNumber交易即可得到一个可用的随机数。作为合约,在实现该方法的时候应该缓存为用户生成的凭据,作为一个Map的key,这个Map的value则应该根据合约自己对随机数的实现自行定义数据结构。 比如,AEDPoS合约在实现ACS6的时候,可以将该Map的value定义为: message RandomNumberRequestInformation { sint64 round_number = 1;sint64 order = 2;sint64 expected_block_height = 3;}其中round_number指示为了生成该用户申请的随机数,应该使用哪一轮(及之后)各个CDC公布的previous_in_value值;order为这个用户申请随机数的RandomNumberProviderContract交易被该轮第几个CDC打包(所以需要使用该轮该次序以后公布的previous_in_value作为随机数生成的“原材料”);expected_block_height则是要告知给用户的需要等待到的区块高度。 ...

July 12, 2019 · 3 min · jiezi

在-Revit-里重现-Forge-Viewer相机的状态

最近,我收到一个客户的需求,希望可以把Viewer的相机状态通过Revit API还原到Revit里。所以我们来看看要如何实现这个要求。在开始之前,你要先知道一些有关于Revit相机的事情: Revit预设的相机FOV值大约为50度,焦距为38.6mm,片幅尺寸为36mm。Revit默认的渲染图片尺寸为6英吋。为了调整Revit相机的FOV值,我们必须利用修改3D视图的裁剪尺寸来完成。因为Revit API没有直接的方法可以修改相机的FOV值。Viewer的相机视角比Revit的相机视角宽。注意:上述关于Revit的相机参数皆为我反复测试得出,Revit没有确切的值,即皆为近似值。 好的,我们转换过程的总思路如下(注意:接下来的步骤适用透视相机模式): Forge Viewer的部分: 从当前视图的 Viewer 相机获取焦距,目标,位置和上向量。 调用 Viewer3D#getFocalLength 以取得焦距。调用 Viewer3D#getState({ viewport: true }) 以取得当前视图必要的相机状态,例如: { "viewport": { "name": "", "eye": [ -14.870469093323, 36.571562767029, -1.2129259109497 ], "target": [ -14.770469665527, 36.571967124939, -1.2129259109497 ], "up": [ 0, 0, 1 ], "worldUpVector": [ 0, 0, 1 ], "pivotPoint": [ -14.770469665527, 36.571967124939, -1.2129259109497 ], "distanceToOrbit": 0.10000024532334, "aspectRatio": 3.1789297658863, "projection": "perspective", "isOrthographic": false, "fieldOfView": 90.68087674208 }}获取当前加载模型的 global offset(注意:Viewer 默认使用 global offset 来调整加载模型的位置,以避免浮点运算精度和 z-buffer fighting的问题): ...

July 11, 2019 · 2 min · jiezi

Unity-NewtonsoftJsonLitJson和SimpleJSON性能对比

Unity中Json库性能对比测试 类库大小对比: 类库文件类型大小NewtonsoftJson.dll353KBLitJson.dll56KBSimpleJSON.cs68KB解析时间对比:执行次数:10000次 测试方法NewtonsoftJsonLitJsonSimpleJSON测试1114ms158ms52ms测试2136ms288ms126ms测试3263ms542ms169ms测试4333ms747ms200ms测试代码: using UnityEngine;using System.Diagnostics;using LitJson;using SimpleJSON;using Newtonsoft.Json.Linq;/// <summary>/// JsonTest/// ZhangYu 2019-07-11/// <para>文章地址:https://segmentfault.com/a/1190000019731298</para>/// </summary>public class JsonTest : MonoBehaviour { public int count = 10000; private Stopwatch watch; private void Start () { watch = new Stopwatch(); string json1 = "{\"id\":10001,\"name\":\"test\"}"; string json2 = "[1,2,3,4,5,6,7,8,9,10]"; string json3 = "{\"id\":10000,\"username\":\"zhangyu\",\"password\":\"123456\",\"nickname\":\"冰封百度\",\"age\":20,\"gender\":1,\"phone\":12345678910,\"email\":\"zhangyu@xx.com\"}"; string json4 = "[\"test2\",[[\"key1\", \"id\"],[\"key2\", \"hp\"],[\"key3\", \"mp\"],[\"key4\", \"exp\"],[\"key5\", \"money\"],[\"key6\", \"point\"],[\"key7\", \"age\"],[\"key8\", \"sex\"]]]"; JsonParseTest(json1); JsonParseTest(json2); JsonParseTest(json3); JsonParseTest(json4); } private void JsonParseTest(string json) { print("json:" + json); bool isArray = json[0] == '['; NewtonsoftJsonTest(json, isArray); LiteJsonTest(json); SimpleJsonTest(json); print("======================"); } private void NewtonsoftJsonTest(string json, bool isArray) { watch.Reset(); watch.Start(); if (isArray) { for (int i = 0; i < count; i++) { JArray jArray = JArray.Parse(json); } } else { for (int i = 0; i < count; i++) { JObject jObj = JObject.Parse(json); } } watch.Stop(); print("NewtonsoftJson Parse Time(ms):" + watch.ElapsedMilliseconds); } private void LiteJsonTest(string json) { watch.Reset(); watch.Start(); for (int i = 0; i < count; i++) { JsonData jData = JsonMapper.ToObject(json); } watch.Stop(); print("LiteJson Parse Time(ms):" + watch.ElapsedMilliseconds); } private void SimpleJsonTest(string json) { watch.Reset(); watch.Start(); for (int i = 0; i < count; i++) { JSONNode jNode = JSON.Parse(json); } watch.Stop(); print("SimpleJson Parse Time(ms):" + watch.ElapsedMilliseconds); }} ...

July 11, 2019 · 1 min · jiezi

AElf共识合约标准ACS4aelf开发者社区

ACS:AElf Contract Standard,AElf合约标准,顾名思义,就是开发AElf智能合约时需要继承和实现的一些接口。所有的ACS都通过protobuf的service定义。 ACS4作为ACS之一,是实现任意一种共识合约时,需要实现的一些接口。本文主要讨论共识标准接口的可行性和AElf中为共识合约的实现所提供的接口及其他支持。抽象共识的思路站在实现区块链共识的角度,我们主要关心三件事: 谁可以产生区块,如PoW共识允许每一个人参与算力竞争,PoS和DPoS共识则要对此做出一定限制;如果可以产生区块,那么应该在什么时候产生,或者说当前的时间能不能开始尝试广播区块;作为一个区块链全节点,应该怎么验证这个区块的合法性。 针对这三点,我们很容易想到三类接口: 输入公钥,判断这个公钥有没有资格产生区块,PoW共识直接返回true就可以了,DPoS可能会对大部分人返回false。输入公钥,返回这个公钥下一次能够产生区块的时间戳,或者返回这个公钥当前能不能产生区块。全节点得到区块头(block header)之后,输入从中提取到的共识数据,验证这个区块的1和2相关信息,即,PoW共识需要验证nonce的合法性,DPoS共识需要验证新区块的生产者身份合法性、生产者是否尊重自己的时间槽,得到验证结果。除此以外,为了得到接口3中的输入,即区块头中的共识数据,至少还需要为区块生产者提供一个生成区块头共识数据的方法。 AElf中的实践首先,AElf的主链选择的共识属于DPoS,本文虽说讨论的是通用共识接口,也免不了倾向于多讨论(AE)DPoS。 其次,所有的共识合约标准上的接口,都是只读的,因为单纯获取这些数据无需改动WorldState。(WorldState是以太坊中的概念,AElf在开发中称用于存储合约的状态的数据库为State DB;除此之外还有Chain DB,用于存储区块本身,包括区块中的交易。) ACS4中合并了接口1和接口2,得到一个接口: rpc GetConsensusCommand (google.protobuf.BytesValue) returns (ConsensusCommand) { option (aelf.is_view) = true;} message ConsensusCommand { int32 NextBlockMiningLeftMilliseconds = 1;// How many milliseconds left to trigger the mining of next block.int32 LimitMillisecondsOfMiningBlock = 2;// Time limit of mining next block.bytes Hint = 3;// Context of Hint is diverse according to the consensus protocol we choose, so we use bytes.google.protobuf.Timestamp ExpectedMiningTime = 4;}很显然,NextBlockMiningLeftMilliseconds的值取决于ExpectedMiningTime(预期出块时间)与当前时间(调用这个接口的时间)的差值。 ...

July 11, 2019 · 1 min · jiezi

AEDPoS合约实现之GetConsensusCommand

正如文章AElf共识合约标准中所述,GetConsensusCommand接口用于获取某个公钥下一次生产区块的时间等信息。 在AEDPoS的实现中,其输入仅为一个公钥(public key),该接口实现方法的调用时间另外作为参考(其实也是一个重要的输入)。AElf区块链中,当系统内部调用只读交易时,合约执行的上下文是自行构造出来的,调用时间也就是通过C#自带函数库的DateTime.UtcNow生成了一个时间,然后把这个时间转化为protobuf提供的时间戳数据类型Timestamp,传入合约执行的上下文中。 事实上,无论要执行的交易是否为只读交易,合约代码中都可以通过Context.CurrentBlockTime来获取当前合约执行上下文传进来的时间戳。 本文主要解释AEDPoS共识如何实现GetConsensusCommand。在此之前,对不了解AElf共识的聚聚简单介绍一下AEDPoS的流程。 AEDPoS ProcessDPoS的基本概念我们不再赘述,假设现在AElf主链通过投票选举出17个节点,我们(暂时地)称之为AElf Core Data Center,简称CDC。(对应eos中的BP即Block Producer这个概念。) 这些CDC是通过全民投票在某个区块高度(或者说时间点)的结果,直接取前17名得到。每次重新统计前17名候选人并重新任命CDC,称为换届(Term)。 在每一届中,所有的CDC按轮(Round)次生产区块。每一轮有17+1个时间槽,每位CDC随机地占据前17个时间槽之一,最后一个时间槽由本轮额外区块生产者负责生产区块。额外区块生产者会根据本轮每个CDC公布的随机数初始化下一轮的信息。18个时间槽后,下一轮开始。如此循环。 Round的数据结构如下: // The information of a round.message Round { sint64 round_number = 1;map<string, MinerInRound> real_time_miners_information = 2;sint64 main_chain_miners_round_number = 3;sint64 blockchain_age = 4;string extra_block_producer_of_previous_round = 7;sint64 term_number = 8;} // The information of a miner in a specific round.message MinerInRound { sint32 order = 1;bool is_extra_block_producer = 2;aelf.Hash in_value = 3;aelf.Hash out_value = 4;aelf.Hash signature = 5;google.protobuf.Timestamp expected_mining_time = 6;sint64 produced_blocks = 7;sint64 missed_time_slots = 8;string public_key = 9;aelf.Hash previous_in_value = 12;sint32 supposed_order_of_next_round = 13;sint32 final_order_of_next_round = 14;repeated google.protobuf.Timestamp actual_mining_times = 15;// Miners must fill actual mining time when they do the mining.map<string, bytes> encrypted_in_values = 16;map<string, bytes> decrypted_previous_inValues = 17;sint32 produced_tiny_blocks = 18;}在AEDPoS合约中有一个map结构,key是long类型的RoundNumber,从1自增,value就是上述的Round结构,CDC产生的每个区块都会更新当前轮或者下一轮的信息,以此推进共识和区块生产,并为共识验证提供基本依据。 ...

July 11, 2019 · 4 min · jiezi

AELF开发者社区AElf区块链分红合约Profit-Contract接口和实现思路

初衷简单说,我们需要一个智能合约来管理所有的分红项目(profit item)。 分红项目是一个代币分配中心:每个分红项目的创建者(creator)可以为该分红项目注册(register)分红的接收地址(receiver address)或其他可以接收分红的分红项目,并为每个接收方设定权重(weight)。之后,每次分红项目的创建者释放(release)分红时,就会为其下注册的接收方地址(可以是一个单独的账户地址,也可以是一个分红项目的虚拟地址(virtual address))按指定的权重分配代币——把待分配的代币一律转化为ELF,或者直接采取Transfer的方式打到相应的地址上,或者等待接收地址的所有人自行获取分红(profit)。每次释放分红后,该分红项目的账期(account period)加一。 我们从中提取出几个概念: 分红项目(profit item)。通过分红合约创建出来的区块链代币分配中心。分红项目创建者(creator)。有权限为创造出来的分红项目注册分红接收地址。分红的接收地址(receiver address)。一个平平无奇的AElf区块链上的用来接收分红代币的账户地址。要注意的是,该部分分红需要接收人自行获取,不会在释放分红的时候自动打到这个账户上(见“获取分红(profit)”)。分红项目的虚拟地址(virtual address)。每个分红项目,都会通过其唯一标识(profit id)映射出来一个只有该分红项目的创建人可以操作的虚拟地址,这个地址仅用来释放分红,没有对应的公私钥对(碰撞出来这个地址的概率可以忽略不计)。子分红项目(sub profit item)。这是一个相对的概念,每个分红项目都可能成为子分红项目。子分红项目可以被其他分红项目分配权重,这样其他分红项目在释放分红时,会为子分红项目的虚拟地址打上一笔代币。获取分红(profit)。作为一个能够接收某个分红的普通用户,需要自行发送交易来获取自己应得的分红,这是为了避免被注册的接收地址过多,释放分红的交易执行超时。权重(weight)。我们选择使用权重来管理每一个分红接收地址能够获取的分红的比例,即(该接收地址被分配的权重/总权重),这样会更加灵活。在必要的时候,我们会限制某一类分红项目的总权重,并把一部分权重分配给固定的(子)分红项目,以达到强行分红的目的——只要这一类分红项目释放了分红,固定的子分红项目就可以至少接收到一定比例的分红。如DApp开发者部署的合约可以选择将一定比例的分红贡献给国库(Treasury)。释放(release)分红。将该分红项目虚拟地址上的余额全部通过Bancor合约转化为ELF,并Transfer给分红接收地址的过程。账期(account period)。账期的时长由每个分红项目自行控制,释放分红后账期自增1。国库(Treasury)。这可能是AElf区块链中最大的分红项目,它可以作为区块生产奖励、合约交易费分红、合约利润分红的子项目,也作为一般的分红项目将它虚拟地址中的余额分配给上一届产生了区块的CDC、参加了AElf竞选的验证节点VDC、参与了AElf竞选的选民等。 接口创建分红项目: rpc CreateProfitItem (CreateProfitItemInput) returns (aelf.Hash) {} ... message CreateProfitItemInput { sint64 profit_receiving_due_period_count = 1;bool is_release_all_balance_everytime_by_default = 2;} message ProfitItem { aelf.Address virtual_address = 1;sint64 total_weight = 2;map<string, sint64> total_amounts = 3;// token_symbol -> total_amountsint64 current_period = 4;repeated SubProfitItem sub_profit_items = 7;aelf.Address creator = 8;sint64 profit_receiving_due_period_count = 9;bool is_release_all_balance_everytime_by_default = 10;bool is_treasury_profit_item = 11;} ...

July 11, 2019 · 2 min · jiezi

热度不如Java网友呛声还有C不能做的事

【IT168 评论】C#是微软公司发布的一种面向对象的、运行于.NET Framework之上的高级程序设计语言,自发布以来受到了程序员的广泛关注。C#与Java有着很多相似之处,例如,单一继承、接口、与Java几乎同样的语法和编译成中间代码再运行的过程。 但是与Java每月被唱衰的热度不同,C#看起来就沉默多了。根据TIOBE编程语言指数来看,从2016年开始,C#的热度就比较平稳,只是每年都会出现周期性的变化。如果从历史排名来看,C#一直是以小步伐在慢慢向前进的。 为什么C#的媒体曝光度不大,但却可以在众多编程语言中保持比较强的竞争优势呢?我想这与C#广泛的应用范围不无关系。那么,C#到底可以做什么呢? 首先,我们先整体来看一下C#的全能功夫单,在桌面端C#有WPF、WinForm、UWP等框架,在服务器端有 ASP. Net Core, ASP . Net Mvc ,WebForm,WCF,在Android、iOS开发端有Xamarin,在游戏开发方面有Unity,在IOT方面还有. Net Core。 据悉,Bing和Azure也有很多是C#写的,大多数金融公司的系统都是使用C#,甚至有人还使用C#写了操作系统。 适用于Windows下的一切 C#的优势很大一部分都来自于Windows,好的C#程序员可以使用C#将Windows的各种性能都挖掘出来,包括各种组件的调用。很少有语言将Windows核心库中的各种组件添加到项目并引用,但是C#依靠强大的Visual Studio就可以做到这一点。友情链接查询 .Net Core将会迎来大爆发 近期,在很多地方都看到有技术人在称:2018年,.Net Core将全面爆发,我们也看到了官方在大力推行.Net Core,2.0版本几乎可以完全符合程序员的需求,相当于.net framework 4.6.1的程度,原本.net framework的类库也几乎全部重写了,性能提升了一大截。 .Net Core可以跑在Linux、MacOS、Windows上,有网友称,他们利用.Net Core在树莓派上进行无人机的通信和飞控应用的开发,在Windows上把程序编译好,可以直接把编译结果传到树莓派的Ubuntu上面运行,而Ubuntu根本连sdk和runtime都不用安装。 C#在游戏领域一骑绝尘 C# + Unity已经快成为了游戏领域的经典搭配,再加上.Net Core服务器的加持,C#的开发体验在一众编程语言中脱颖而出。C#在游戏开发领域本来就有很好的使用历史,再加上性能不俗,在游戏领域一骑绝尘也是意料之后。 网友一句话评价C# 如何用一句话来评价C#的使用范围呢?我们来看看网友都有哪些神评论吧! 网友1:可以问个问题吗?有哪些使用C#不能完成的工作? 网友2:C#:我不是说你XXX,而是在座的各位,在windows服务器作为基础的情况下,你们都是垃圾。 网友3:这么说吧,我们公司正在用.net core写区块链…… 网友4:即使你用其它语言来做开发,C#也会给你带来神助攻的感觉!

July 9, 2019 · 1 min · jiezi

C是否快被时代所淘汰

C#是微软公司发布的一种面向对象的、运行于.NET Framework之上的高级程序设计语言。C#的发音为“C sharp”,模仿音乐上的音名“C#”(C调升),是C语言的升级的意思。其正确写法应和音名一样为“C#”,但大多数情况下“#”符号被井号“#”所混用;两者差别是:“#”的笔画是上下偏斜的,而“#”的笔画是左右偏斜。C#由C语言和C++派生而来,继承了其强大的性能,同时又以.NET框架类库作为基础,拥有类似Visual Basic的快速开发能力。 C#由安德斯·海尔斯伯格主持开发,微软在2000年发布了这种语言,到今天已经经历了近18个年头。在这18个年头里其他编程语言也在不断的发展,可谓是百花齐放。那么,有人可能就会担心了,C#会不会因此退出历史的舞台呢?我简单谈谈自己的看法。友情链接查询 首先,C#有点类似Java,属于一种比较庞大的语言。一开始你会觉得功能太多,但是随着大家用熟了、形成了一些固有的模式,就会感觉到很灵活,很够用。大项目、小游戏、类库都可以搞定,不用切换技术方案。使用方便,容器够用,基本库很多,不用自己管理内存,Exception的设计很科学。需要极端性能的时候也可以申请非托管内存……要啥有啥。C#是静态类型语言,且微软爸爸的.net虚拟机优化很到位,另一种开源虚拟机mono性能也很不错。据测试C#执行时间约是C语言的4倍,lua是C的7倍,Python是C的14倍。大家感受一下。(这个性能测试并不准确,但是语言执行效率的相对关系可以参考。)。C#依然保留了在栈上分配变量的形式,同时支持值类型和引用类型。动态语言和静态语言的优势算是都占上了。以上,是它的优点;接下来就让我们说一说它的缺点。 用C#编写的程序必须运行在.NET平台。但是现在的用户机器上大部分是没安装.NET平台。也就是用C#编写的程序大部分用户是不能用的!但我想仅这一点应该不至于C#被历史淘汰掉,而且近几年C#也在不停的发展着。因此,C#理论上是很难被淘汰的,至少我这么认为。

July 9, 2019 · 1 min · jiezi

nodejs使用aes128ecb加密如何在c中解密

最近需要在nodejs上加密jwt,C#端解密jwt得到用户信息 class JwtService extends Service { encrypt(content) { const secretkey = this.app.config.jwt.key // 唯一(公共)秘钥 const cipher = crypto.createCipher('aes-128-ecb', secretkey) // 使用aes128加密 let enc = cipher.update(content, 'utf8', 'hex') // 编码方式从utf-8转为hex; enc += cipher.final('hex')// 编码方式转为hex; return enc }}却发现C#端怎么也解密不了,一直报错,改了一整天,后来终于发现,nodejs端加密用的key其实在使用之前已经使用md5加密了一次,服务端如果需要使用这个key解密,则需要也同样使用MD5加密 public static string AesDecrypt(string content, string key) { // nodejs aes加密默认的key使用了md5加密,所以C#解密的key也要默认使用md5 MD5 md5 = new MD5CryptoServiceProvider(); byte[] output = Encoding.UTF8.GetBytes(key); byte[] keyArray = md5.ComputeHash(output); byte[] toEncryptArray = HexStringToBinary(content); RijndaelManaged des = new RijndaelManaged(); des.Key = keyArray; des.Mode = CipherMode.ECB; des.Padding = PaddingMode.PKCS7; ICryptoTransform cTransform = des.CreateDecryptor(); byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); return Encoding.UTF8.GetString(resultArray); }代码使用了一个函数把16进制转成字节数组 ...

July 8, 2019 · 1 min · jiezi

C-三个Timer

在C#中存在3种常用的 Timer : System.Windows.Forms.TimerSystem.Timers.TimerSystem.Threading.Timer零、System.Windows.Forms.Timer这个 Timer 是单线程的,也就是说只要它运行,其他线程就要等着。 这个 Timer 有如下特点: 完全基于UI线程,定时器触发时,操作系统把定时器消息插入线程消息队列中,调用线程执行一个消息泵提取消息,然后发送到回调方法Tick中;使用 Start 和 Stop 启动和停止 Timer;UI操作过长会导致 Tick 丢失;可以使用委托Hook Tick事件;精确度不高;通过将 Enabled 设置为 True,使 Timer 自动运行从上面的第一个特点可以得知,该 Timer 会造成 WinForm UI 假死,因此如果需要定时处理大量计算或者大量IO操作的任务,不建议使用该 Timer ,接下来我们看一个例子体会一下在IO操作的情况下出现的假死情况: 我们在Form中放入两个Button 一个Lable和一个Timer private void Button_Click(object sender, EventArgs e){ timer.Interval = 1000; timer.Tick += Timer_Tick; timer.Start();}private void Timer_Tick(object sender, EventArgs e){ for (int i = 0; i < 10000; i++) { File.AppendAllText(Directory.GetCurrentDirectory()+"test.txt", i.ToString()); this.label_output.Text = "当前操作:插入数字" + i; }}我们单击计算按钮,我们会发现WinForm出现了假死(无法移动窗口、按钮无法点击等) ...

July 1, 2019 · 2 min · jiezi

Entity-Framework-一对多关系映射

EF中关系映射也是一个很关键的内容,关系映射和属性映射一样,也是在 OnModelCreating 中配置映射。EF中的关系映射有如下三种: One-to-Many Relationship(一对多)Many-to-Many Relationship(多对多)One-to-One Relationship(一对一)我们今天先讲解 One-to-Many Relationship(一对一关系) 零、创建所需类所有实体类公用的抽象基类public abstract class Base{ public int Id { get; set; } public DateTime CreateTime { get; set; } public DateTime ModifiedTime { get; set; }}客户类和订单类public class Customer : Base{ public string Name { get; set; } public string Email { get; set; } public virtual ICollection<Order> Orders { get; set; }}public class Order : Base{ public byte Quanatity { get; set; } public int Price { get; set; } public int CoustomerId { get; set; } public virtual Customer Customer { get; set; }}一、One-to-Many Relationship创建Map映射类在编写代码之前,我们先分析一下客户和订单的关系。一个客户可以有多个订单,但一个订单只能属于一个客户,所以我们用到了EF中的 HasRequired,一个客户又存在多个订单,因此也使用到了 WithMany ,同时 Order 表中有 CustomerId 作为外键,因此我们用到了 HasForeignKey 。根据我们的分析,编写代码如下: ...

July 1, 2019 · 2 min · jiezi

控制反转依赖注入简明教程

在面向对象中IOC是一个重要的设计思想。这篇文章将带领大家快速掌握控制反转和依赖注入。 注:代码基于c#零、IocIoc 英文是 Inversion of Control,中文是控制反转。所谓控制反转,就是A类中有对B类方法的调用,我们调用之前一般都会先new,这样就增加了类和类之间的耦合度。为了降低耦合度,将A类对B类的的控制权交给Ioc容器,让双方都依赖Ioc容器。 一、DIDI 的英文是 Dependency Injection,中文是依赖注入。依赖注入是实现Ioc的一种方式,也是常用的方式。依赖注入的方式主要有三种:构造函数注入、接口注入 和 属性注入。(因为这篇文章知识一个简单的入门,因此我们不讲解这三种注入)我们来通过一个例子,来看一下依赖注入的好处: 故事:小吴是一个公司的CEO,每天都需要司机开车送他上下班,开始他只有一个司机,每次司机生病,他就只能自己开车上下班。因此小吴设立了一个司机部门,部门中有多名司机,由司机部门给小吴指派司机。 分析:从上面的故事可以分析得出,刚开始小吴是依赖者,司机是被依赖者,小吴依赖于小刚。后来通过增加司机部门这个Ioc容器,小吴和小刚之间的关系变为了,小吴依赖于司机部门。 我们通过代码看一下(这里使用到了 .NET 依赖注入容器 AutoFac): static void Main(string[] args){ //接小吴 IContainer driverCont = DriverDepartment(); //司机部门分配一个司机给CEO小吴 CE0_Wu wu = driverCont.Resolve<CE0_Wu>(); wu.Car(); Console.Read();}/// <summary>/// 司机部门/// </summary>/// <returns></returns>private static IContainer DriverDepartment(){ ContainerBuilder builder = new ContainerBuilder(); builder.RegisterType<CE0_Wu>(); builder.RegisterType<Driver>().As<IDriver>(); return builder.Build();}}/// <summary>/// 抽象以来/// </summary>public class Driver : IDriver{/// <summary>/// 开车/// </summary>public void Drive(){ Console.WriteLine("开车送老板");}}public interface IDriver{void Drive();}/// <summary>/// 小吴/// </summary>public class CE0_Wu{private IDriver driver;public CE0_Wu(IDriver driver){ this.driver = driver;}public void Car(){ driver.Drive();}}

July 1, 2019 · 1 min · jiezi

开源仪表盘DashboardCWF

Simple Dashboard(一个简单的仪表盘)为什么说简单呢,其实这个Dashboard并没有用到什么太高深的技术,核心是在计算上,一个核心的坐标点计算公式之前已经在我的博客有过分享了:“ 已知圆心和半径手绘一个圆形-C#/WF”,直接套用即可。 Dashboard(仪表盘)显示效果还是不错的,样式是参考了PowerBI的一个案例。其中所有的颜色都是可以自由搭配替换的。 Customized Property(自定义属性)Expected(期望值)标识仪表盘的上限值。 Real(已达到的值/进度值) BottomTitleColor(底部标题颜色)指最底部的文字字体颜色。 BottomTitleFont(底部标题字体)指最底部的文字的字体。 Indicator(指针角度)这个说的并非指针真实的角度,但是却和指针的大小关联,值越大标识指针越大,最大不可超过200. IndicatorColor(指针颜色)这个属性指的是指针的颜色。 IndicatorFill(填充式指针)标识指针是单线条还是填充式的,默认是填充的,也就是如上所展示的,设置False为单线条样式,如下展示。 InnerBackground(内圆弧填充色)指的是内测的圆弧背景色,非中心圆。 OuterColor(外圆线条颜色)指的是如图所指的外侧的浅蓝色线条的颜色。 InnerColor(内圆线条颜色)指的是如图所指的内侧的深蓝色线条的颜色。 InnerRoundColor(中心圆的填充色)指的是中心圆的背景色 ProgressColor(进度条颜色)指的是当前进度条划过的颜色 ProgressDisplayModel(进度条显示模式)默认模式是Inner模式,有三种可供选择,分别是:Inner、Center、Fill。以下分别展示了三种模式: Inner:Center:Fill: ScaleExpectedColor(期望值的刻度颜色)如图中所指,标识没有达到时刻度值和刻度线的颜色。 Override Property(重写的属性)除了以上重写的属性之外,还使用了从Control继承过来的一些属性。 Text(文本)仪表盘中间的Title内容。 ForeColor(文本颜色)仪表盘中间的Title颜色。 Font(文本样式)仪表盘中间的Title字体。 最后总的来说自定义性还是比较高的,可以自由搭配颜色,界面效果参考自PowerBI,其他内容为原创,转发请注明来源! OpenSourceGit:https://github.com/qylost/Das... 本地下载:本地下载

June 27, 2019 · 1 min · jiezi

EFCore相关命令

EF实体框架有3种类型,Data First、Model First、Code First。从项目实体文件生成数据库表的命令 (Code First)第一步:需要引入的NuGet包 Microsoft.EntityFrameworkCoreMicrosoft.EntityFrameworkCore.Tools(EF工具包,创建实体)数据库驱动 Sql Server 请安装 Microsoft.EntityFrameworkCore.SqlServerMySql/MariaDB请安装Pomelo.EntityFrameworkCore.MySql (2.0及以上版本)其他数据库请查看https://docs.microsoft.com/zh...第二步: 生成实体文件和DBContext(数据库上下文) 见文章第三步:通过迁移来创建数据库,需在工具->NuGet包管理器->程序包管理器控制台选择执行命令所在的项目下输入以下命令 Add-Migration init迁移成功可以看见在项目根目录下添加了一个Migrations文件夹 更新迁移到数据库,执行命令 Update-Database然后删除Migrations文件夹即可 从数据库表生成项目实体文件的命令 (DB First)需要引入的NuGet包 Microsoft.EntityFrameworkCoreMicrosoft.EntityFrameworkCore.Tools(EF工具包,创建实体)数据库驱动 Sql Server 请安装 Microsoft.EntityFrameworkCore.SqlServerMySql/MariaDB请安装Pomelo.EntityFrameworkCore.MySql (2.0及以上版本)其他数据库请查看https://docs.microsoft.com/zh...需在工具->NuGet包管理器->程序包管理器控制台中选择执行命令所在的项目下输入以下命令 Scaffold-DbContext "Server=.;database=test;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models或 Scaffold-DbContext "Data Source=rdsqjywf72g48u7zrw5alo.sqlserver.rds.aliyuncs.com,3433; Database=chehuoyiv3-dev; User ID=chy_dev; Password=Chy123456;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models命令格式为:Scaffold-DbContext "数据库连接字符串" EF组件名(Microsoft.EntityFrameworkCore.SqlServer/Pomelo.EntityFrameworkCore.MySql/等等) -OutputDir 输出文件夹名称 [补充]添加Nuget包方式 :方式一:在Nuget包管理器中搜索,例如搜索:Microsoft.EntityFrameworkCore.SqlServer,然后安装方式二:在程序包管理器控制台中输入Install-Package Microsoft.EntityFrameworkCore.SqlServer (可能不成功)

June 21, 2019 · 1 min · jiezi

C给枚举加自定义特性

通常我们需要定义一组特定值。采用枚举再好不过了。它可以让我们很方便直观的管理一组固定的值。如果我们需要对应输出枚举值的汉语意思或者颜色样式等,我们可以这样实现: 首先定义一个枚举类enum @enum{ Update = 1, Insert = 2}方式一(if语句)if (@enum == 1){ Console.Write("更新");}else (@enum == 2){ Console.Write("新增");}方式二(switch语句)switch(@enum){ case 1: Console.Write("更新"); break; case 2: Console.Write("新增"); break;}方式三(采用字典)Dictionary<int, string> dic = new Dictionary<int, string>{ [1] = "更新", [2] = "新增"};Console.Write(dic[@enum]);现在我们用一种更加优雅的方式来实现,给枚举加特性在System.ComponentModel命名空间下有一个特性Description,用来指定属性或事件的描述。 enum @enum{ [Description("修改")] Update = 1, [Description("新增")] Insert = 2}我们可以写一个扩展,用来获取Description特性,这用反射来读取Description的值,这个是复用性的/// <summary>/// 获取特性 (DescriptionAttribute) 的说明;如果未使用该特性,则返回枚举的名称。可指定的默认值。/// </summary>/// <param name="enum"></param>/// <param name="def">默认值</param>/// <returns></returns>public static string Description(this Enum @enum, string def = ""){ Type enumType = @enum.GetType(); int value = int.Parse(Enum.Format(enumType, Enum.Parse(enumType, @enum.ToString()), "d")); FieldInfo fieldInfo = enumType.GetField(Enum.GetName(enumType, value)); if (fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute), false) is DescriptionAttribute descriptionAttribute) { return descriptionAttribute.Description; } return def != "" ? def : @enum.ToString();}那么在使用的时候我们将可以很方便的获取枚举值对应的描述信息 ...

June 19, 2019 · 1 min · jiezi

Entity-Framework-小知识四

在EF中并没有提供包含索引和过滤索引的创建方法,那么我们就么发创建了吗?答案是否定的,我们可以通过迁移类进行创建包含索引和过滤索引。首先我们通过 Add-Migration 命令创建一个空的迁移类,然后在 Up方法中输入如下代码: Sql($"CREATE NONCLUSTERED INDEX [{IndexName}] ON [dbo].[User]([Name] INCLUDE ([IdNumber]))");在 Down 方法中输入如下代码: DropIndex("dbo.User","IndexName")

June 18, 2019 · 1 min · jiezi

Entity-Framework-索引

Entity Framwework 6 设置和使用索引,是一个比较 egg 疼的事情,为什么这么说呢?因为Entity Framwework 6的不同版本有不同的设置和使用方法,按照版本来划分,有三种方法: EF6 方法EF6.1.x方法EF6.2.x方法EF6EF6中设置索引比较麻烦,我们需要先进行code first 迁移,然后在迁移类中的 Up 方法中输入如下代码: //创建索引且值唯一CreateIndex("dbo.User","Name",unique:true);//创建复合索引,索引名称为 **NameAndIdNumber**CreateIndex("dbo.User",new []{"Name","IdNumber"},name:"NameAndIdNumber");在 Down 方法中输入如下代码: DropIndex("dbo.User","Name");DropIndex("dbo.User",new []{"Name","IdNumber"});注:EF6中通过迁移类创建的索引无法重命名EF6.1.x该版本定义索引的方法如下: public virtual void OnModelCreating(DbModelBuilder modelBuilder){ modelBuilder.Entity<User>().Property(p => p.Name).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute() { IsUnique=true }));}上面这段代码的意思是,给User表创建一个唯一索引Name。同样上面的代码也可以单独定义在一个类中: public class UserMap : EntityTypeConfiguration<User>{ public UserMap() { Property(p => p.Name).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute() { IsUnique=true })); }}我们前面知道在EF6中创建的索引无法重命名,那么在EF6.1.x中创建的索引是否可以重命名吗?答案是当然可以,我们只需在前一类中的 Up 和 Down 方法写入如下代码即可: public override void Up(){ RenameIndex(table:"db.User",name:"Name",newName:"NameIndex");}public override void Down(){ RenameIndex(table:"db.User",name:"NameIndex",newName:"Name");}EF6.2.x在EF6.2.X中创建索引比较简单,只需要调用 HasIndex 方法即可。 ...

June 17, 2019 · 1 min · jiezi

初探C-dynamic关键字

“C# 4 引入了一个新类型 dynamic。该类型是一种静态类型,但类型为 dynamic 的对象会跳过静态类型检查。"——Microsoft Docs[1]静态的"动态"dynamic是C# 4中加入的一个新的“上下文关键字”,它指示动态类型。我们可以直接创建一个dynamic类型的变量,可以将任意对象赋值给它。如下所示: dynamic dynVar1 = 1;dynamic dynVar2 = new Object();当我们在代码中使用了dynamic类型时,就是在告诉编译器关闭对该对象的运行时检查,而在运行时确定对象类型。比如,以下代码可以成功编译: dynamic numericDyn = 80;numericDyn.Greet();但是在这段代码会抛出一个运行时异常RuntimeBinderException,提示我们类型int不包含方法Greet()的定义。这说明了在底层,dynamic变量类型在运行时仍然是确定的,也就是说,C#依然是静态类型化语言。当我们在一个dynamic变量上调用GetType()时,会输出该对象的实际类型。如下所示: Console.WriteLine(numericDyn.GetType());// Console prints System.Int32要注意的是,在使用dynamic类型的变量时,编译器无法对该变量所指向的对象作任何类型推断,也无法枚举方法与属性。因此,IntelliSense和重构工具在此时失效。 下面这个例子可以清晰地说明这一点。我们首先创建一个类,再用一个dynamic类型变量装载一个Cat对象的引用,再试图重构Cat中的一个方法。完整代码: using System;namespace DynamicKeywordTest{ class Program { static void Main(string[] args) { dynamic dynCat = new Cat(); dynCat.Meow(); } class Cat { public void Meow() { Console.WriteLine(String.Format("Cat {0} meows.", this.GetHashCode())); } } }}使用Visual Studio的重构工具,将方法Meow()重命名为Honk(),我们发现dynCat.Meow()并没有更改。 var, object和dynamic关键字var用于编译时类型推断。编译器在编译时自动确定var代表的类型。如下列代码: var varInt = 1;var varStr = "Hello, Vars!";关闭优化编译,使用.NET Reflector反编译得到 ...

June 17, 2019 · 1 min · jiezi

程序员过关斩将你的业务是可变的吗

请不要跟我说用ES或者其他,其实很多中小公司的业务就是如此,就是基于mysql或者sqlserver 来搞这样的业务业务场景不知道通过D妹子的阐述,大家了解情况了没。这里菜菜再详细说一下。D妹子的程序记录了订单的log来供其他业务(比如统计)使用,这里就以统计业务来说,OrderLog表设计如下: 列名数据类型描述OrderIdnvarchar(100)订单号,主键UserIdint下单用户idAmountint订单的金额其他字段省略... 除此之外还有一个用户信息表UserInfo,设计如下: 列名数据类型描述UserIdint用户id,主键ProvinceIdint用户省的idCityIdint用户市的idCountyIdint用户区县的id涉及到拆单等复杂的订单操作,表的设计可能并非如此,但是不影响菜菜要说的事变数的业务现在假如要统计某个省的订单总数,sql如下: select count(0) from OrderLog o inner join UserInfo u on o.UserId=u.UserId where ProvinceId=@ProvinceId有问题吗,sql没问题,这时候用户A的省市区县信息突然变了(也许是在其他地区买房,户口迁移了),也就是说UserInfo表里的信息变了,那用以上的sql统计用户A以前省市区县的订单信息是不是就会出错了呢?(产品狗说在哪下的订单就属于哪的订单) 业务的定位以上的问题你觉得是不是很简单呢?只要稍微修改一下表也许就够了。但是,菜菜要说的不是针对这一个业务场景,而是所有的业务场景的设计。那你有没有想过为什么D妹子的设计会出现这样的问题呢? 深刻理解业务才能避免以上类似的错误发生,一定要深刻理解不变和可变的业务点。 拿D妹子的统计来说,你的业务是统计区域的订单数,这个业务在产品设计上定义的是不变性,也就是说在行为产生的那个时间点就确定了业务性质,这个业务的性质不会随着其他变而变。具体到当前业务就是:用户在X省下的订单不会随着用户区域信息的变化而变化,说白了就是说用户在X省生成的订单永远属于X省。 谈到业务性质的不变性,对应的就有业务的可变性。假如你开发过类似于QQ空间这样的业务,那肯定也做过类似访客的功能。当要显示访客记录的时候,访客的名称在多数情况的设计中属于可变性的业务。什么意思呢?也就是说一个用户修改了姓名,那所有显示这个用户访问记录的的地方姓名都会同时改变。 说到这里,各位再回头看一下D妹子的业务,这里又牵扯到一个系统设计的问题,众所周知,一个好的系统设计需要把业务的变化点抽象提取出来,D妹子订单统计的业务变化点在于用户的省市区县会变化,订单的金额、订单号等信息不会变化。所以你们觉得是不是D妹子的数据表可以修改一下呢? 数据表的改进改进用户信息按照以上的阐述,D妹子业务的变化点在于用户的省市区域信息,所以可以把用户信息的表抽象提取出来,主键不再是用户id 列名数据类型描述Idint主键Id,主键UserIdint用户idProvinceIdint用户省的idCityIdint用户市的idCountyIdint用户区县的id这样的话用户订单log表中就变为 列名数据类型描述OrderIdnvarchar(100)订单号,主键UserBIdint对应用户表中的主键idAmountint订单的金额其他字段省略... 这样设计的话,如果用户的省市区县信息有变动,相应的用户信息表中会存在多条用户省市区县数据 这里的用户信息表并非是用户对象的主表,而是根据订单业务衍生出来的表改进业务数据表根据业务的变性和不变性,既然把订单区域统计的业务定义为不变的业务性质,那订单的log表完全可以这样设计 列名数据类型描述OrderIdnvarchar(100)订单号,主键UserIdint下单用户idProvinceIdint用户省的idCityIdint用户市的idCountyIdint用户区县的idAmountint订单的金额其他字段省略... 写在最后各位读到这里,可能会感觉菜菜这次写的其实很鸡肋,但是,D妹子的场景却是真实环境中遇到的问题。问题的本质还是变性业务和非变性业务的定义和划分,和架构设计一样,数据库的设计其实也需要把变动的业务存储点进行抽象,其实应该说是抽离出来。 希望大家有所收获 --菜菜 添加关注,查看更精美版本,收获更多精彩

June 14, 2019 · 1 min · jiezi

Entity-Framework-小知识三

零、乐观并发在单服务器上运行的站点,为了防止出现脏读现象,我们一般使用Lock语句关键字,但是如果在分布式站点上使用Lock语句关键字是不起作用的,因为程序锁住了服务器1数据库实例,但服务器2并不知道服务器1已被锁住,这样依然会出现脏读现象。这时我们就用到了EF的乐观并发。 EF中解决并发有两种方式: 利用并发Token;利用行版本的方式代码如下: public class EfDbContext : DbContext{ public EfDbContext() { Database.SetInitializer(new DropCreateDatabaseIfModelChanges<EfDbContext>()); } public virtual void OnModelCreating(DbModelBuilder modelBuilder) { // 利用并发Token modelBuilder.Entity<Users>().Property(t=>t.Name).IsConcurrencyToken(); // 利用行版本 modelBuilder.Entity<Users>().Property(t=>t.Name).IsRowVersion(); }}注:在并发量不是很大的时候可以使用EF的乐观并发,在访问量很大的时候应该使用其他技术处理并发问题。

June 14, 2019 · 1 min · jiezi

Entity-Framework-小知识二

零、基于代码配置基于代码配置是EF6新增的一个特性,操作步骤如下: 创建DbConfig派生类;配置默认连接工厂;设置Database Provider;设置数据库初始化器;1. 创建DbConfig派生类public class EF6Config:DbConfiguration{ public EF6Config(){}}接下来使用 DbConfigurationType 属性在上下文类中设置基于代码的配置类: [DbConfigurationType(typeof(EF6Config))]public partial class EF6DbContext:DbContext{ public EF6DbContext():base("name=EF6DbContext"){} }2. 配置默认连接工厂使用 SetDefaultConnectionFactory 方法设置默认连接工厂(以SQL SERVER 数据库为例): public class EF6Config:DbConfiguration{ public EF6Config() { this.SetDefaultConnectionFactory(new System.Data.Entity,Infrastructure.SqlConnectionFactory()); }}3. 设置Database Provider使用 SetProviderServices() 方法配置数据库提供程序: public class EF6Config:DbConfiguration{ public EF6Config() { this.SetDefaultConnectionFactory(new System.Data.Entity,Infrastructure.SqlConnectionFactory()); this.SetProviderServices("System.Data.SqlClient",System.Data.Entity.SqlServer.SqlProviderServices.Instance); }}4. 设置数据库初始化器在使用 code first 的情况下,可以使用基于代码的配置数据库的初始值: public class EF6Config:DbConfiguration{ public EF6Config() { this.SetDefaultConnectionFactory(new System.Data.Entity,Infrastructure.SqlConnectionFactory()); this.SetProviderServices("System.Data.SqlClient",System.Data.Entity.SqlServer.SqlProviderServices.Instance); this.SetDatabaseInitializer<EF6DbContext>(new CustomDBInitializer(EF6DbContext)()); }}注:.config 中 <entityframework> 的配置优于代码配置,也就是说,如果同时在 .config 中和代码中都设置了配置选项,则优先使用 .config 中的设置。

June 13, 2019 · 1 min · jiezi

Entity-Framework-约定

约定,类似于接口,是一个规范和规则,使用Code First 定义约定来配置模型和规则。在这里约定只是记本规则,我们可以通过Data Annotaion或者Fluent API来进一步配置模型。约定的形式有如下几种: 类型发现约定主键约定关系约定复杂类型约定自定义约定零、类型发现约定在Code First 中。我们定义完模型,还需要让EF上下文你知道应该映射那些模型,此时我们需要通过 DbSet 属性来暴露模型的。如果我们定义的模型由继承层次,只需要为基类定义一个DbSet属性即可(如果派生类与基类在同一个程序集,派生类将会被自动包含),代码如下: public class Department{ public int DepartmentId { get; set; } public string Name { get; set; } public virtual ICollection<Blog> Blogs { get; set; }}public class EfDbContext : DbContext{ public EfDbContext() { } public DbSet<Department> Departments { get; set; }}当然,有时候我们不希望模型映射到数据库中,这时我们可以通过Fluent API 来忽略指定的模型映射到数据库中,代码写在EF上下文中: protected override void OnModelCreating(DbModelBuilder modelBuilder){ modelBuilder.Ignore<Department>();}一、主键约定Code First 会根据模型中定义的id,推断属性为主键(如果类中没有id属性,会查找定义成类名称+id的属性,将这个属性作为主键)。如果主键类型是int 或者 guid 类型,主键将会被映射为自增长标识列。例如我们上一小节中定义的类 Department,类中没有名称为id的属性,但是存在名称为类名称+id的属性DepartmentId,因此DepartmentId属性,将会被映射为自增长的主键。如果一个类中既没有id属性,也没有类名+id的属性,那么代码在运行时将会报错,因为EF没有找到符合要求的字段创建主键。 二、关系约定在数据库中,我们可以通过多张表的关联查询出数据,这多张表之间的关联,就是他们的关系。同样,也可以在模型中定义这样的关系。EF中定义关系要使用到导航属性,通过导航属性可以定义多个模型之间的关系。大部分情况下我们会将导航属性和外键属性结合在一起使用。导航属性的命名规则如下:导航属性名称+主体主键名称 或者 主体类名+主键属性名称 或者 主体主键属性名。当EF检测出外键属性后,会根据外键属性是否为空来判断关系,如果外键可以为空,那么模型之间的关系将会配置成可选的,Code First 不会再关系上配置级联删除。看一个简单的代码: ...

June 13, 2019 · 2 min · jiezi

Entity-Framework复杂类型属性映射

零、创建项目必须代码public class BaseModel{ public int Id { get; set; } public DateTime CreateDateTime { get; set; }}public class Address{ public string Street { get; set; } public string City { get; set; } public string ZipCode { get; set; }}public class User:BaseModel{ public string Name {get;set;} public string Birthdate {get;set;} public string IdNumber {get;set;} public Address Address {get;set;}}以上代码在ORM中称为组合类,EF会将这两个类映射在一张表中。当Code First发现不能推断出类的主键,并且没有通过Data Annotations或Fluent API注册主键,那么该类型将被自动注册为复杂类型。 注意:复杂类型检测要求该类型不具有引用实体类型的属性,还要求不可引用另一类型的集合属性复杂类型的在数据库中映射的列名称为:负载类型类名_属性名我们接下来创建 DbContext 类 public class EfDbContext : DbContext{ public EfDbContext() { Database.SetInitializer(new DropCreateDatabaseIfModelChanges<EfDbContext>()); } public DbSet<User> Users { get; set; }}创建完DbContext类后,我们编写将数据存入数据库的方法: ...

June 13, 2019 · 2 min · jiezi

Entity-Framewor简单属性映射

本节我们只介绍在EF中比较常见的映射 零、表名映射默认情况下可以不配置表名,我们的模型名称将会作为数据库的表名。但是大部分项目会要求数据库表名称的规范,例如我们要将模型 User 在数据库中映射为 Users,那么我们可以这么做,在派生类上下文中的 OnModelCreating 中进行如下定义: modelBuilder.Entity<User>().ToTbale("Users");一、主键映射表的主键我们一般习惯使用 Id 或者以 Id 结尾的方式来命名,EF默认情况下会将 Id 或以 Id 结尾的属性作为主键,如果两者都存在的话,默认会以 Id 作为主键。但是,还存在如下几种情况: 设置联合主键;主键为 int 类型,但是不是自增长的,而是手动分配的。针对上面两种情况,我们分别进行如下配置: //设置联合主键modelBuilder.Entity<User>().HasKey(k => new{ Id=k.Id, UserId=k.UserId});//手动分配主键值modelBuilder.Entity<User>().HasKey(k => k.Id).Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);DatabaseGeneratedOption 是枚举类型,值如下: 值说明Identity标识列Computed计算列None手动分配值二、数值映射数据库中的数值类型有很多种,C#中也有很多数值类型,但是我们无法直接将C#中的数值类型转换为数据库中的数值类型。那么怎么将C#数值类型映射为数据库数值类型呢?这里我们以 C# float 为例,来看一下代码: modelBuilder.Entity<User>().Property(p=>p.Float);通过上面的代码,我们将 C# float 类型映射为了数据库的 real 类型。下表是C#数值类型对应的数据库的数值类型: C#数值类型数据库数值类型intintdoublefloatfloatrealdecimaldecimal(18,2)Int64bigint我们看到上表中有一个C#数值类型 decimal 对应的数据库数值类型是 decimal(18,2) ,括号中的2代表小数点后保留2位,但是在一些情况下我们需要保留小数点后面N位,这时我们可以这么做: modelBuilder.Entity<User>().Property(p=>p.Money).HasPrecision(18,4);三、字符串映射当我们未对string类型的属性配置映射时,默认的数据库类型是 nvarchar(max),但是大部分情况下不会使用这个默认的映射。举几个例子来讲解一下怎么来改变这个默认映射。 字段不可为空//设置Name属性在数据库映射不可为空modelBuilder.Entity<User>().Property(p=>p.Name).IsRequired();字段可为空//设置Birthday属性在数据库映射可为空modelBuilder.Entity<User>().Property(p=>p.Birthday).IsOptional();四、日期映射EF中的日期类型在数据库中默认映射为Date,但是数据库中的日期类型还有很多,并且有时候我们需要将日期类型映射为数据库其他类型,那么我们该怎么做呢?这里我们以映射为 DateTime 为例: modelBuilder.Entity<User>().Property(p=>p.CreateDateTime).HasColumnType("DATETIME");注:数值类型和日期类型属于值类型,因此我们不需要通过 IsRequired 来配置映射字段不可为空,因为默认就是不为空的。但是可以通过 IsOptional 设置可为空。

June 13, 2019 · 1 min · jiezi

个人收款之微信小微商户

微信支付小微商户介绍微信支付商户申请面向线下小微商户开放,无需营业执照,个人即可开通。符合条件的微信支付服务商可为小微商户发起接入申请。点击开通小微商户 一、小微商户能力介绍1. 快速进件(审核开通只需要几分钟)2. 支持零钱、借记卡、信用卡支付方式3. 交易手续费 0.38%4. 每日结算款T+1日自动提现至商户个人银行卡 小微商户普通商户收款方式银行卡银行卡交易费率0.38% 点击开通小微商户标准费率 查看对照表支付方式零钱、借记卡、信用卡零钱、借记卡、信用卡支付权限JSAPI支付 Native支付 付款码支付JSAPI支付 Native支付 付款码支付 H5支付 APP支付管理能力可登录商家助手小程序可登录商家助手小程序 可登录微信支付商户平台二、申请规则1. 小微商户仅支持服务商通过API发起接入申请2. 申请提交后,平台一般会在5分钟内完成审核,若通过,API会返回签约二维码,小微商户需扫码确认方可完成签约3. 小微商户签约后,平台会实时开通交易能力,可由服务商为小微商户发起JSAPI支付、付款码支付、Native支付 小微商户普通商户申请方式服务商通过API申请服务商通过页面申请审核耗时5分钟内 点击开通小微商户48小时内账户验证方式平台快捷验证(无需商户操作)打款验证+回填金额签约微信签约微信签约 商户平台签约三、申请材料点击注册登陆查看资料表格 五、常见问题点击查看

June 10, 2019 · 1 min · jiezi

开源-FreeSql-配套工具基于-Razor-模板实现最高兼容的生成器

FreeSql 经过半年的开发和坚持维护,在 0.6.x 版本中完成了几大重要事件: 1、按小包拆分,每个数据库实现为单独 dll; 2、实现 .net framework 4.5 支持; 3、同时支持 MySql.Data、MySqlConnector 的实现; 4、自定义导航属性关系的配置; 5、配套工具 FreeSql.Tools 发布; 本文主要讲解第5项《FreeSql.Tools》,大主角往往在最后才出现!!! 拆分小包在此之前一直被吐槽 FreeSql 臃肿,没有小包开发理念。其实我是一点也不承认这种评价,虽然刚开始只有一个 FreeSql.dll,但是在开发和规划上简单了很多。 有一条开发原则这样讲道:过早优化是恶梦! 大概意思是无论做什么项目,不要想着一开始就过度系统的、规范的执行。从外界来看是正规了,但是进度和稳定性会大大折扣。可以不信我,但是请一定要相信前人的总结啊!!! 从之前的一个 dll 到拆分成小包,我们总共耗时两天,虽然都在一个项目内开发,但其实耦合性并不高,so easy!! 车到山前必有路,时机到了自然会拆。这个时机也是奠定 FreeSql 走出了稳定关键的一步。这样会有更多人愿意加入 FreeSql 阵营。 各数据库单独包、延时加载包;FreeSql.Extensions.LazyLoadingFreeSql.Provider.MySqlFreeSql.Provider.PostgreSQLFreeSql.Provider.SqlServerFreeSql.Provider.SqliteFreeSql.Provider.Oracle支持 .netframework 4.5早期 FreeSql 主要是在 .net core 最方便的 ORM!NETStandard 是新的标准,然而前段时间微软又说 ..net5 将合并。。。变化真的太快。 在实现拆分小包后,其实 FreeSql 的模块更加清淅,并且依赖项非常之少,然后比较容易的做出了 4.5 framework 的适配。 目前支持的版本: Package NameVersionFreeSql.Provider.MySqlNETStandard2.0、net452FreeSql.Provider.PostgreSQLNETStandard2.0、net45FreeSql.Provider.SqlServerNETStandard2.0、net451FreeSql.Provider.SqliteNETStandard2.0、net45FreeSql.Provider.OracleNETStandard2.0、net45FreeSql.Extensions.LazyLoadingNETStandard2.0、net45MySqlConnector 的实现mysql 是一个神奇的流行数据库,在 .net 阵营中使用量排名老二。mysql 的版本五花八门,从 5.6 开始有了不同的分支,分支的出现使得 ado.net 驱动不通用。 很多人不推荐使用 MySql.Data 官方驱动,但是 FreeSql 一直在使用官驱,并且支持了所有 5.6 类型,包括 enum/set 等。 ...

June 4, 2019 · 2 min · jiezi

程序员过关斩将论商品促销代码的优雅性

背景介绍据我所知,几乎所有的互联网公司都带有和电商有关的项目,而且在大多数公司里面还是举足轻重的重头戏,比如京东,淘宝。既然有电商项目,必然会涉及到商品,一旦有商品就会有各种促销活动,比如 满100减20,三八妇女节9折等等类似活动。作为一个coder怎么才能在实现产品狗的需求下,最小改动代码,最优雅的实现呢。今天菜菜不才,就D妹子的问题献丑一番。以下以.netCore c#代码为例,其他语言类似。 D妹子版本首先D妹子有一个商品的对象,商品里有一个价格的属性,价格的单位是分 class Product { //其他属性省略 public int Price { get; set; } }下面有一个满100减20的活动,在结算价格的时候代码是这样的 public int GetPrice() { Product p = new Product(); int ret = p.Price; if (p.Price >= 100*100) { ret = ret - 20 * 100; } return ret; }有问题吗?按照需求来说没有问题,而且计算的结果也正确。但是从程序艺术来说,其实很丑陋。现在又有一个全场9折的活动,恰巧有一个商品参与了以上两个活动,而且还可以叠加使用(假设活动参与的顺序是先折扣后满减)。这时候D妹子的代码就变成了这样 public int GetPrice() { Product p = new Product(); //9折活动 int ret = p.Price * 90 / 100; //满减活动 if (ret >= 100 * 100) { ret = ret - 20 * 100; } return ret; }假如现在又来一个类似活动,那这块代码还需要修改,严重违反了开放关闭原则,而且频繁修改已经上线的代码,bug的几率会大大增高。这也是D妹子领导骂她并且让她codereview的原因。 ...

June 3, 2019 · 3 min · jiezi

程序员过关斩将请不要随便修改基类

初级版本这是玩家的抽象基础类,这个设计很好,把一些玩家共有的特性抽象出来 //玩家的基础抽象类 abstract class Player { //玩家的级别 public int Level { get; set; } //其他属性代码省略一万字 }这是新加需求:10级可以跳跃,具体跳跃动作是客户端做处理 //玩家的基础抽象类 abstract class Player { //玩家的级别 public int Level { get; set; } //其他属性代码省略一万字 //新加玩家跳跃动作,由于需要到达10级所以需要判断level public virtual bool Jump() { if (Level >= 10) { return true; } return false; } }这种代码初级人员很容易犯,有什么问题呢? 跳跃的动作被添加到了基类,那所有的子类就都有了这个行为,如果子类机器人玩家不需要这个跳跃的行为呢?为了新需求,修改了基类,如果每次需求都需要修改基类,时间长了,项目大了,这个是比较要命的。优化版本由于需求是增加玩家一个行为,根据上一节的介绍,我们应该了解到,行为在代码级别更倾向于用接口来表示。而且不是所有的玩家类型都需要附加跳跃这个行为。据此优化如下: //玩家跳跃的行为 interface IJump { bool Jump(); } //玩家的基础抽象类 abstract class Player { //玩家的级别 public int Level { get; set; } //其他属性代码省略一万字 } //真实玩家 class PersonPlayer : Player, IJump { public bool Jump() { if (Level >= 10) { return true; } return false; } }不错,到此我们已经避免了初级人员所犯的错误了,每种玩家类型可以根据需要自行去扩展行为,改天产品狗在加一个10级玩家可以飞的行为,顶多在加一个IFly的行为接口,然后实现即可。但是这样的设计就没有问题了吗?有,当然有 ...

May 31, 2019 · 2 min · jiezi

分享一款-程序员秒懂-很优雅的翻译软件

软件名称:QTranser取名QTranser,代表快速翻译的意思,实际上真的是最快(方便)的翻译软件~~ 简介:我为什么要写这款软件:其实还是有不少朋友使用windows写代码。 写代码的时候经常会翻译英文单词。 但是所有的翻译方式都“不够优雅” 要么是局限在浏览器内。要么会在屏幕中央弹出一个小窗口。 软件使用方式:Ctrl+CCtrl+C 可能是最优雅的翻译方式:这款翻译软件 利用Ctrl+C将单词复制进剪切板,将翻译结果显示在windows任务栏: 这样就很好地解决了全局翻译问题,在任何应用程序中,只要你能够复制得到字符串,它就能帮你翻译。。 Ctrl+Q翻译详情页面如果翻译内容过长,您可以使用Ctrl+Q键快速打开/关闭翻译详情页面,查看更详细的翻译结果: 驼峰字段的处理代码中经常会遇到驼峰字段,如:trimStart ,翻译器也会将驼峰字段拆分为:trim Start并进行翻译。。 我还定义了另外两个快捷键:Ctrl+B(一键百度) Ctrl+G(一键谷歌) 大家用一下就知道,选中单词或者句子,按下快捷键,就可以自动帮您打开浏览器,并搜索。。 有没有极大地方便了我们地学习? 下一步会增加另外两个功能:1、按下Ctrl+c得到翻译结果的同时能够朗读英文(当然可以选择打开或者关闭)。 2、截图翻译,对那些无法Ctrl+c的单词,可以截图。。 下载最后附上下载连接:https://github.com/xyfll7/QTr... 对源代码有兴趣的童鞋,也可以浏览源代码。

May 29, 2019 · 1 min · jiezi

程序员过关斩将你的面向接口编程一定对吗

妹子开始抱怨起来业务背景妹子的游戏是个对战类的游戏,其中有一个玩家的概念,玩家可以攻击,这个业务正是妹子开始挠头的起点 第一次需求产品经理:玩家有很多属性,例如:身高,性别 blalalala ,玩家可以攻击其他玩家。YY妹子写程序也是很利索,一天就把程序搞定了,而且还抽象出一个palyer的基类出来,堪称高级程序员必备技能。 //玩家的基础抽象类 abstract class Player { public string Name { get; set; } //. //. //. //玩家的攻击 public abstract void Attack(); } //真实玩家 class PersonPlayer : Player { public override void Attack() { //to do something return; } }第二次需求产品经理:游戏里我需要增加机器人玩家来增加游戏在线的人数,机器人属性和真实玩家一样,但是攻击不太一样这个需求修改还是难不住YY妹子,没过几天代码改好了,增加了一个机器人玩家的类,用到了OO的继承。在这里为玩家抽象类点赞 class RobotPlayer : Player { public override void Attack() { //修改攻击内容等 to do something return; } }第三次需求产品经理:我要创建一批类似玩家的怪物,没有真实玩家的那些属性,但是和真实玩家一样有攻击行为这个时候YY妹子终于意识到攻击是一种行为了,需要抽象出接口来了。 //攻击接口 interface IAttack { void Attack(); } //玩家的基础抽象类 abstract class Player { //其他属性代码省略一万字 } //真实玩家 class PersonPlayer :Player, IAttack { public void Attack() { //to do something return; } } //机器人玩家 class RobotPlayer :Player, IAttack { public void Attack() { // to do something return; } } //怪物玩家 class MonsterPlayer : IAttack { public void Attack() { // to do something return; } }到了这里,我们遇到了大家耳熟能详的面向接口编程,没错,这个做法是对的。这也是设计的一大原则:程序依赖接口,不依赖具体实现。这里要为YY继续点赞。顺便说一下,在多数情况下,很多同学就到此为止了第四次需求产品经理:我现在要设计玩家的攻击方式了,目前有远程攻击,近程攻击,贴身攻击这三类,其他需求 blalalalala。据说此刻YY妹子的心里是一万头羊驼飘过的状态。这次要怎么设计呢?这也是菜菜要说的重点部分。现在我们需要静下心来思考一番了,为什么我们使用了面向接口编程,遇到这次需求,程序还是需要修改很多东西呢? ...

May 26, 2019 · 1 min · jiezi

C-Winfrom在x32和x64环境中读取Excel

1.OleDb读取(必须装Office,Office版本不同,参数设置不一样) 2.Microsoft.Office.Interop.Excel读取(支持win7 x64)win7 x32中报 .无法将类型为“System.__ComObject”的 COM 对象强制转换为接口类型“Excel.Application”。此操作失败的原因是对 IID 为“{000208D5-0000-0000-C000-000000000046}”的接口的 COM 组件调用 QueryInterface 因以下错误而失败: 不支持此接口 (异常来自 HRESULT:0x80004002 (E_NOINTERFACE))。在网上找解决方法:4位系统下,有些组件(微软的)只有32位版本,64位的应用程序调用不了32位的COM组件,返回的结果就是没有注册类别。 在64位系统中,VS2008默认会生成64位的程序,你可以改变程序的部署平台为×86来生成32位的程序。解决方法:在“解决方案”-》“开发项目的名称”,鼠标右击,单击“属性”-》“生成”-》“目标平台”,把“AnyCPU”改成“×86”,重新编译程序即可。 (问题没有解决) .Microsoft.Office.Interop.Excel;的嵌入互操作类型改为false报错如下: 未能加载文件或程序集“Interop.Office, Version=99.1.0.0, Culture=neutral, PublicKeyToken=null”或它的某一个依赖项。系统找不到指定的文件。3.NPOI读取Excel 可支持(目前没有发现环境问题)

May 24, 2019 · 1 min · jiezi

CNET基于Topshelf创建Windows服务的守护程序作为服务启动的客户端桌面程序不显示UI界面的问题分析和解决方案

本文首发于:码友网--一个专注.NET/.NET Core开发的编程爱好者社区。 文章目录C#/.NET基于Topshelf创建Windows服务的系列文章目录: C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载 (1)在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务) (2)C#/.NET基于Topshelf创建Windows服务的守护程序作为服务启动的客户端桌面程序不显示UI界面的问题分析和解决方案 (3)前言在上一篇文章《在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)》的最后,我给大家抛出了一个遗留的问题--在将TopshelfDemoService程序作为Windows服务安装的情况下,由它守护并启动的客户端程序是没有UI界面的。到这里,我们得分析为什么会出现这个问题,为什么在桌面应用程序模式下可以显示UI界面,而在服务模式下没有UI界面? 分析问题(Session 0 隔离)通过查阅资料,这是由于Session 0 隔离作用的结果。那么什么又是Session 0 隔离呢? 在Windows XP、Windows Server 2003 或早期Windows 系统时代,当第一个用户登录系统后服务和应用程序是在同一个Session 中运行的。这就是Session 0 如下图所示: 但是这种运行方式提高了系统安全风险,因为服务是通过提升了用户权限运行的,而应用程序往往是那些不具备管理员身份的普通用户运行的,其中的危险显而易见。 从Vista 开始Session 0 中只包含系统服务,其他应用程序则通过分离的Session 运行,将服务与应用程序隔离提高系统的安全性。如下图所示: 这样使得Session 0 与其他Session 之间无法进行交互,不能通过服务向桌面用户弹出信息窗口、UI 窗口等信息。这也就是为什么刚才我说那个图已经不能通过当前桌面进行截图了。 潜在的问题解决方案在了解了Session 0 隔离之后,给出一些有关创建服务程序以及由服务托管的驱动程序的建议: 1、与应用程序通信时,使用RPC、命名管道等C/S模式代替窗口消息2、如果服务程序需要UI与用户交互的话,有两种方式:①用WTSSendMessage来创建一个消息框与用户交互②使用一个代理(agent)来完成跟用户的交互,服务程序通过CreateProcessAsUser创建代理。 并用RPC或者命名管道等方式跟代理通信,从而完成复杂的界面交互。3、应该在用户的Session中查询显示属性,如果在Session 0中做这件事,将会得到不正确的结果。4、明确地使用Local或者Global为命名对象命名,Local/为Session/<n>/BaseNamedObject/,Global/为BaseNamedObject/5、将程序放在实际环境中测试是最好的方法,如果条件不允许,可以在XP的FUS下测试。在XP的FUS下能工作的服务程序将很可能可以在新版系统中工作,注意XP的FUS下的测试不能检测到在Session 0下跟视频驱动有关的问题 本文我们的服务程序将通过CreateProcessAsUser创建代理来实现Session 0隔离的穿透。 在项目[TopshelfDemoService]中创建一个静态扩展帮助类ProcessExtensions.cs,代码如下: using System;using System.Runtime.InteropServices;namespace TopshelfDemoService{ /// <summary> /// 进程静态扩展类 /// </summary> public static class ProcessExtensions { #region Win32 Constants private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; private const int CREATE_NO_WINDOW = 0x08000000; private const int CREATE_NEW_CONSOLE = 0x00000010; private const uint INVALID_SESSION_ID = 0xFFFFFFFF; private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; #endregion #region DllImports [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] private static extern bool CreateProcessAsUser( IntPtr hToken, String lpApplicationName, String lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandle, uint dwCreationFlags, IntPtr lpEnvironment, String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] private static extern bool DuplicateTokenEx( IntPtr ExistingTokenHandle, uint dwDesiredAccess, IntPtr lpThreadAttributes, int TokenType, int ImpersonationLevel, ref IntPtr DuplicateTokenHandle); [DllImport("userenv.dll", SetLastError = true)] private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit); [DllImport("userenv.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr hSnapshot); [DllImport("kernel32.dll")] private static extern uint WTSGetActiveConsoleSessionId(); [DllImport("Wtsapi32.dll")] private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken); [DllImport("wtsapi32.dll", SetLastError = true)] private static extern int WTSEnumerateSessions( IntPtr hServer, int Reserved, int Version, ref IntPtr ppSessionInfo, ref int pCount); #endregion #region Win32 Structs private enum SW { SW_HIDE = 0, SW_SHOWNORMAL = 1, SW_NORMAL = 1, SW_SHOWMINIMIZED = 2, SW_SHOWMAXIMIZED = 3, SW_MAXIMIZE = 3, SW_SHOWNOACTIVATE = 4, SW_SHOW = 5, SW_MINIMIZE = 6, SW_SHOWMINNOACTIVE = 7, SW_SHOWNA = 8, SW_RESTORE = 9, SW_SHOWDEFAULT = 10, SW_MAX = 10 } private enum WTS_CONNECTSTATE_CLASS { WTSActive, WTSConnected, WTSConnectQuery, WTSShadow, WTSDisconnected, WTSIdle, WTSListen, WTSReset, WTSDown, WTSInit } [StructLayout(LayoutKind.Sequential)] private struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } private enum SECURITY_IMPERSONATION_LEVEL { SecurityAnonymous = 0, SecurityIdentification = 1, SecurityImpersonation = 2, SecurityDelegation = 3, } [StructLayout(LayoutKind.Sequential)] private struct STARTUPINFO { public int cb; public String lpReserved; public String lpDesktop; public String lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } private enum TOKEN_TYPE { TokenPrimary = 1, TokenImpersonation = 2 } [StructLayout(LayoutKind.Sequential)] private struct WTS_SESSION_INFO { public readonly UInt32 SessionID; [MarshalAs(UnmanagedType.LPStr)] public readonly String pWinStationName; public readonly WTS_CONNECTSTATE_CLASS State; } #endregion // Gets the user token from the currently active session private static bool GetSessionUserToken(ref IntPtr phUserToken) { var bResult = false; var hImpersonationToken = IntPtr.Zero; var activeSessionId = INVALID_SESSION_ID; var pSessionInfo = IntPtr.Zero; var sessionCount = 0; // Get a handle to the user access token for the current active session. if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0) { var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO)); var current = pSessionInfo; for (var i = 0; i < sessionCount; i++) { var si = (WTS_SESSION_INFO)Marshal.PtrToStructure(current, typeof(WTS_SESSION_INFO)); current += arrayElementSize; if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive) { activeSessionId = si.SessionID; } } } // If enumerating did not work, fall back to the old method if (activeSessionId == INVALID_SESSION_ID) { activeSessionId = WTSGetActiveConsoleSessionId(); } if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0) { // Convert the impersonation token to a primary token bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, ref phUserToken); CloseHandle(hImpersonationToken); } return bResult; } public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = null, string workDir = null, bool visible = true) { var hUserToken = IntPtr.Zero; var startInfo = new STARTUPINFO(); var procInfo = new PROCESS_INFORMATION(); var pEnv = IntPtr.Zero; int iResultOfCreateProcessAsUser; startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO)); try { if (!GetSessionUserToken(ref hUserToken)) { throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed."); } uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE); startInfo.lpDesktop = "winsta0\\default"; if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false)) { throw new Exception("StartProcessAsCurrentUser: CreateEnvironmentBlock failed."); } if (!CreateProcessAsUser(hUserToken, appPath, // Application Name cmdLine, // Command Line IntPtr.Zero, IntPtr.Zero, false, dwCreationFlags, pEnv, workDir, // Working directory ref startInfo, out procInfo)) { iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed. Error Code -" + iResultOfCreateProcessAsUser); } iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); } finally { CloseHandle(hUserToken); if (pEnv != IntPtr.Zero) { DestroyEnvironmentBlock(pEnv); } CloseHandle(procInfo.hThread); CloseHandle(procInfo.hProcess); } return true; } }}修改ProcessHelper.cs为如下代码: ...

May 24, 2019 · 4 min · jiezi

FreeSql-aop功能介绍

前言FreeSql 是一个功能强大的 .NETStandard 库,用于对象关系映射程序(O/RM),支持 .NETCore 2.1+ 或 .NETFramework 4.6.1+(QQ群:4336577)。 据了解,用户使用很少问问题,编码过程中,因业务阻塞,情有可原;因框架使用问题阻塞,得不偿失。我们的口号:做 .net 最方便的 ORM!愿每一位开发者嘴角上扬????! 整体功能IFreeSql 是核心,提供原始用法;FreeSql.DbContext 是扩展包,提供面向对象的用法(像EF);FreeSql.Repository 也是扩展包,提供仓储+工作单元用法(实际上和 DbContext 是一个扩展包);FreeSql.Connection.Extensions 也是扩展包,提供像 Dapper 一样的用法;源码地址:https://github.com/2881099/FreeSql,可从这里链向上面介绍的各个仓库。 fsql= new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document.db;Attachs=xxxtb.db;Pooling=true;Max Pool Size=10") .UseAutoSyncStructure(true) //自动迁移(CodeFirst) .Build();AOP 功能今天上场的是 AOP 已有的功能介绍,未来为会根据用户需求不断增强。 审计 CRUD马云说过,996是修福报。对于多数程序员来说,加班是好事。。。起码不是闲人,不会下岗。 当如果因为某个 sql 骚操作耗时很高,没有一个相关的审计功能,排查起来可以说无从下手,福报与你紧紧相随(哈哈)。 FreeSql 支持简单的类似功能: fsql.Aop.CurdAfter = (s, e) => { if (e.ElapsedMilliseconds > 200) { //记录日志 //发送短信给负责人 }};是的,只需要一个事件,就可以对全局起到作用。 除了 CurdAfter,还有一个 CurdBefore (在执行 sql 之前触发)。 审计迁移脚本FreeSql 自带迁移功能,那么迁移的 SQL 语句长啥样,你可能会好奇。 ...

May 24, 2019 · 1 min · jiezi

C根据用户信息生成token和cookie的方法

在前后端分离的项目里,我们请求接口的流程一般是: 用户使用用户名密码登录信息正确,接口返回token请求需要登录验证的接口,将token放到header里一起请求接口这里介绍一下,在webapi项目里,token是怎么生成的 项目的引用里,右键:管理NuGet程序包搜索JWT,安装即可,要注意项目的.NetFrameWork 要大于等于4.6 代码如下public class TokenInfo{ public TokenInfo() { UserName = "jack.chen"; Pwd = "jack123456"; } public string UserName { get; set; } public string Pwd { get; set; }}public class TokenHelper{ public static string SecretKey = "This is a private key for Server";//这个服务端加密秘钥 属于私钥 private static JavaScriptSerializer myJson = new JavaScriptSerializer(); public static string GenToken(TokenInfo M) { var payload = new Dictionary<string, dynamic> { {"UserName", M.UserName},//用于存放当前登录人账户信息 {"UserPwd", M.Pwd}//用于存放当前登录人登录密码信息 }; IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJsonSerializer serializer = new JsonNetSerializer(); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); return encoder.Encode(payload, SecretKey); } public static TokenInfo DecodeToken(string token) { try { var json = GetTokenJson(token); TokenInfo info = myJson.Deserialize<TokenInfo>(json); return info; } catch (Exception) { throw; } } public static string GetTokenJson(string token) { try { IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder); var json = decoder.Decode(token, SecretKey, verify: true); return json; } catch (Exception) { throw; } }}使用cookie也是一样,用户登录之后,用特定的方法生成cookie,返回到浏览器,浏览器每次请求接口或者访问页面的时候,都会带上cookie信息,用于身份验证c#生成cookie的方法: ...

May 24, 2019 · 2 min · jiezi

常用的公式编辑器软件

互联网时代,我们越来越依赖机器。用机器学习的好处就是方便、快捷。作为理科学生,在课堂上见证老师通过编辑器熟练输入复杂公式。想到我们是否可以通过计算机软件,将数学笔记做整理,方便在笔记本上浏览和复习呢?下面小编介绍一款专业公式编辑器软件,让我们高效学习、复习数学这一门重要学科。亿图公式编辑器(EdrawMath)是一款功能强大的数学公式编辑器。相比较WPS自带的公式编辑器,它可以帮我们实现复杂公式和符号的编写,无缝兼容Office办公文档。相信这款颜值较高的软件和贴心的设计,在数学学习的道理上能够帮助到你。 EdrawMath,好用的数学笔记公式编辑器结合做题出现的错题,可以将易错题通过EdrawMath做整理,还有常考知识重点做分类,将重点标记出来。将写好的公式可以通过云收藏在线保存公式,方便二次编辑。通过整理笔记的过程也加深了知识的记忆,云端保存的笔记方便我们随时浏览进行多次复习。数学学习的重点就是基础的掌握,具体到公式的应用后期的解题思路才会愈加清晰。 如何用EdrawMath做数学笔记1.汉字部分可支持键盘直接输入,方便我们重点标注说明和做分类。重要内容可以加粗、斜体标注,方便查找关键词。 2.输入公式按着从左到右顺序,注意切换到英文输入法。功能栏找到“上下标模板”,找到对应的数学符号,填补上数字即可。 3.编辑好的公式记得在线保存,点击右边新增分类,自定义名称《数学笔记》。点击加号,输入公式名称即可。 以上就是给大家介绍的用亿图公式编辑器做数学笔记的基本操作,无需下载亿图软件在线即可轻松编辑公式,同时方便同学们将重点知识进行分类整理和有针对性的做复习。现在,如果你也想要用一款线上工具来整理数学笔记,也可以用EdrawMath来提升自己的学习效率!

May 22, 2019 · 1 min · jiezi

FreeSql-新功能介绍贪婪加载五种方法

前言FreeSql 在经过6个月的开发和朋友们的工作实践,不断的改进创新,目前拥有1500个左右单元测试方法,且每个方法内又复盖不同的测试面。 今天介绍 FreeSql 各种贪婪加载的姿势,作下总结。本节内容对应的还有【延时加载】,贪婪加载和他本该在一起介绍,开发项目的过程中应该双管齐下,才能写出高质量的程序。有关延时加载,日后有空再单独编写。 FreeSql是一个功能强大的NETStandard库,用于对象关系映射程序(O/RM),便于开发人员能够使用 .NETStandard 对象来处理数据库,不必经常编写大部分数据访问代码。 [√] 支持 CodeFirst 迁移;[√] 支持 DbFirst 从数据库导入实体类,支持三种模板生成器;[√] 采用 ExpressionTree 高性能读取数据;[√] 支持深入的类型映射,比如pgsql的数组类型,堪称匠心制作;[√] 支持丰富的表达式函数;[√] 支持导航属性查询,和延时加载;[√] 支持同步/异步数据库操作方法,丰富多彩的链式查询方法;[√] 支持读写分离、分表分库,租户设计;[√] 支持多种数据库,MySql/SqlServer/PostgreSQL/Oracle/Sqlite;贪婪方法一:Dto 映射查询Select<Tag>().Limit(10).ToList(a => new TestDto { id = a.Id, name = a.Title });Select<Tag>().Limit(10).ToList(a => new TestDto());Select<Tag>().Limit(10).ToList(a => new TestDto { });Select<Tag>().Limit(10).ToList(a => new TestDto() { });Select<Tag>().Limit(10).ToList<TestDto>();像这种映射支持单表/多表。 查找规则,查找属性名,会循环内部对象 _tables(join 查询后会增长),以 主表优先查,直到查到相同的字段。 如: A, B, C 都有 id,Dto { id, a1, a2, b1, b2 },A.id 被映射。也可以指定 id = C.id 映射。 ...

May 16, 2019 · 2 min · jiezi

Entity-Framework-Core自动迁移

2019/05/15,EFCore 2.2.4有两种方式:1.使用Migrate()方法if (DbContext.Database.GetPendingMigrations().Any()){ DbContext.Database.Migrate(); //执行迁移}Migrate()方法使用前需在程序包管理控制台执行Add-migration迁移命令。之后程序每次启动,GetPendingMigrations()都会去检测是否有待迁移内容,有的话,自动应用迁移。 GetPendingMigrations方法官方文档说明获取在程序集中定义但尚未应用于目标数据库的所有迁移。Migrate()方法官方文档说明将上下文的任何挂起的迁移应用于数据库。 如果数据库尚不存在,将创建它。请注意,此API与DbContext.Database.EnsureCreated()互斥。 EnsureCreated不使用迁移来创建数据库,因此以后无法使用迁移更新创建的数据库。 2.使用EnsureCreated()方法//如果成功创建了数据库,则返回trueDbContext.Database.EnsureCreated()此方法不需要先执行Add-migration迁移命令,如果数据库不存在,则自动创建并返回true。如果已经创建了数据库后,又改动了实体Model和之前的库存在冲突,要注意删库让它自动重建,否则会报错。注意,使用EnsureCreated()创建的数据库,是不带有__EFMigrationsHistory表的,所以使用该方法后无法再使用迁移更新已经创建的数据库 EnsureCreated方法官方文档说明确保上下文的数据库存在。 如果存在,则不采取任何措施。 如果它不存在,则创建数据库及其所有模式。 如果数据库存在,则不会确保它与此上下文的模型兼容。

May 16, 2019 · 1 min · jiezi

Entity-Framework初体验

零、初体验新建控制台程序,名称为:MyFirstEF在NuGet中搜索 Entity Framework,如下图: 创建 Blog 类:public class Blog{ public int Id { get; set; } public string Name { get; set; } public string Url { get; set; } public DateTime? CreatedTime { get; set; } public double Double { get; set; } public float Float { get; set; }}创建一个继承自EF上下文的类,此上下文是与数据库交互的一个中间桥梁,我们可以称之为会话,并且为每一个模型公开一个DbSet:public class EfDbContext : DbContext{ public EfDbContext() { } public DbSet<Blog> Blogs { get; set; }}注:上下文派生类中定义DbSet有如下三种方式: //用DbSet属性public class EfDbContext : DbContext{ public EfDbContext() { } public DbSet<Blog> Blogs { get; set; }}//用IDbSet属性public class EfDbContext : DbContext{ public IDbSet<Blog> Blogs { get; set; }}//只读属性public class EfDbContext : DbContext{ public DbSet<Blog> Blogs { get {return Set<Blog>();} }}在主函数上添加如下代码:static void Main(string[] args){ using (var efDbContext = new EfDbContext()) { efDbContext.Blogs.Add(new Blog() { Name = "张三", Url = "http://www.baidu.com" }); efDbContext.SaveChanges(); }}运行控制台程序,如果未出现任何报错,则会在VS对应的本地数据库中看到新创建的 Blogs 表和一条新数据。 ...

May 14, 2019 · 1 min · jiezi

现代编程语言的值传递与引用传递

现代编程语言对于值传递与引用传递的支持程度是比较不同的 首先介绍值传递与引用传递的概念 值传递将变量a传递到其他的函数并对其更改,不能影响a的值 引用传递在其他的作用域对传入的变量a的更改可以影响a的值 Note: 在这里的值的概念,对于原始类型,指的就是字面的值,如1,2,'a'; 而对于动态内存分配/类,则指的是指向这个分配内存/类的引用,而非解引用后指向的内存/类所保存的值 C语言C语言本身只支持值传递,但是通过指针这一概念,通过解引用可以达到引用传递的效果 C++作为C语言的超集发展起来的语言,C++支持C语言的值传递与指针传递,同时C++还添加了引用传递(某种意义上是指针的语法糖),所以C++实际上通过两种语法支持引用传递 下面演示C/C++的值传递与引用传递 #include <iostream>#include <vector>#include <algorithm>using namespace std;/// 基础类型热引用void swap_ref(int &a, int &b) { int t = a; a = b; b = t;}/// 类的引用void swap_ref(string &a, string &b) { string t = a; a = b; b = t;}/// 值传递void swap_val(int a, int b) { int t = a; a = b; b = t;}/// 类的值传递void swap_val(string a, string b) { string t = a; a = b; b = t;}/// 基于指针进行引用传递void swap_ptr(int *a, int *b) { int t = *a; *a = *b; *b = t;}void swap_ptr(string *a, string *b) { string t = *a; *a = *b; *b = t;}int main() { int a = 1; int b = 2; swap_ref(a, b);//引用传递 printf("%d %d\n", a, b); swap_val(a, b);//值传递 printf("%d %d\n", a, b); string x = "x", y = "y"; swap_ref(x, y);//引用传递 cout << x << " " << y << endl; swap_val(x, y);//值传递 无效果 cout << x << " " << y << endl; /// \brief 使用指针本身进行值传递 通过解引用达到了解引用的效果 swap_ptr(&a, &b);//通过指针引用传递 printf("%d %d\n", a, b); swap_ptr(&x, &y);//指针的引用传递 cout << x << " " << y << endl; return 0;}$ ./main.exe2 12 1y xy x1 2x yNotes: ...

May 11, 2019 · 2 min · jiezi

Entity-Framework简介

零、什么是Entity FrameworkEntity Framework (简称EF),是.NET的 Object/Relational Mapping 实体框架(简称ORM),可以在 SQL Server、MySQL、Oracle、等数据库上使用。可以将数据作为业务对象和实体进行操作,使用LINQ进行查询,使用C#进行操作和检索。 一、领域建模方式Entity Framework 有三种领域建模方式:Code First、Model First和Data First Code First Code First 可以通过类来描述模型,然后通过类来创建数据库,这种类简称为POCO(Plain Old CLR Object)。POCO中的C是指 .NET Framework公共语言运行时(Common Language Runtime,CLR)中的一个简单对象。POCO对域对象使用尽可能简单的类,可以包含属性、方法等,但是方法不能实现持久化逻辑,也就是说POCO也可以包含业务逻辑。Code First 优点如下: 可以创建一个更富有逻辑、更灵活的应用程序;因为没有自动生成难以修改的代码,所以我们可以对代码完全控制;只需要定义映射,其余一切交给Entity Framework来处理;可以用修改代码的方式来修改数据库;可以使用它来映射表结构到一个已存在的数据库。Model First Model First 允许我们使用实体设计器在空模型中创建模型实体,及其关系和继承层次结构,然后创建数据库。优缺点如下: 无法控制实体和数据库,因为自动生成的代码难以修改,但是对于小型且简单的项目,它仍行之有效;在实体中添加额外的功能,不得不修改T4模板或者使用部分类来完成;数据库模型的更改不是最佳选择,因为是由模型定义了数据库。Data First Data First 使我们能够从现有数据库创建模型,减少了自动生成代码所需编写的代码量,也限制了我们使用生成代码的结构。优缺点如下: 如果已有DBA设计的数据来单独开发或已存在数据库,将作为首选通过EDM向导为我们创建实体、关系和继承层次结构,修改映射后还可以生成实体;要在实体中添加额外的功能,必须通过T4修改模板或者使用部分类;数据库的手动更改变为可能,如果要修改数据库表结构,只需要从数据库更新实体模型即可。

May 11, 2019 · 1 min · jiezi

编译原理学习一去除代码中的注释

前言开始学习编译原理了耶~关于编译原理的所有练习,按照老规矩,还是用我最喜欢的C#语言来实现,运行在.NetCore平台上~关于这个系列的所有代码已经上传到github了,项目主页: https://github.com/Deali-Axy/CompilerConstructionLearning本次题目对C或C++等高级程序设计语言编写的源程序中的//注释和/…/注释进行删除,保留删除后的源程序。要求以文件形式进行保存。思路分析程序主要功能就是消除已经编写好的源程序中的注释。在源程序中注释有两种形式,一种是单行注释,用“//”表示,另一种是多行注释,用“/…/”表示。针对这两种形式,程序中用了if..else..语句加以判断,并做出相应的处理。在这里还有可能出现另一种情况,上述两种注释符号可能出现在引号中,出现在引号中的注释符号并没有注释功能,因此在引号中出现的注释符号不应该被消除。所以,这次编写的程序将要分三种情况分析。第一种情况,单行注释:if (ch != temp){ // 这里就是单行注释 ofile.put(ch); ch = ifile.get();}或者 if (ch != temp){ /* 这里就是单行注释 */ ofile.put(ch); ch = ifile.get();}第二种情况,块注释:if (ifile.fail() || ofile.fail()){ cerr << "open file fail\n"; return EXIT_FAILURE; /*返回值EXIT_FAILURE(在cstdlib库中定义),用于向操作系统报* 告打开文件失败*/}第三种情况,行后注释:ifile.close(); // 关闭文件ofile.close();cout << "/////*////ret/rtr////";system("pause");return 0;还有一个关键的注意点可以看到这一行 cout << "/////*////ret/rtr////";这个字符串用双引号包起来的代码中有很多斜杠,所以要避免将这些斜杠识别为注释。这里我用的方法是在处理注释前先把包含注释符号的字符串替换掉,等注释删除之后,再添加回去。 实现代码注释写得很详细啦,配合上面的思路分析,我就不再继续分析代码了~ var sReader = new StreamReader(filePath);var newSource = "";var inBlock = false;var replaceFlag = false;var tempLine = ""; // 用于保存被替换的特殊行代码while (!sReader.EndOfStream){ var line = sReader.ReadLine(); if (line.Length == 0) continue; // 去除空行 var quotationPattern = "^(.*?)\".*//.*\""; var quotationResult = Regex.Match(line, quotationPattern); if (quotationResult.Success) { System.Console.WriteLine("替换特殊代码,双引号中包裹注释斜杠"); tempLine = quotationResult.Groups[0].Value; replaceFlag = true; line = Regex.Replace(line, quotationPattern, REPLACEMENT); } // 单行注释 if (line.Trim().StartsWith(@"//")) continue; if (line.Trim().StartsWith(@"/*") && line.EndsWith(@"*/")) continue; // 注释块 if (Regex.Match(line.Trim(), @"^/\*").Success) inBlock = true; if (Regex.Match(line.Trim(), @"\*/$").Success) { inBlock = false; continue; } // 行后注释 // 使用非贪婪模式(.+?)匹配第一个// var pattern = @"^(.*?)//(.*)"; // var pattern = @"[^(.*?)//(.*)]|[^(.*?)/\*(.*)\*/]"; var result = Regex.Match(line, pattern); if (result.Success) { System.Console.WriteLine("发现行后注释:{0}", result.Groups[2]); line = result.Groups[1].Value; } // 还原被替换的代码 if (replaceFlag) { System.Console.WriteLine("还原特殊代码"); line = line.Replace(REPLACEMENT, tempLine); replaceFlag = false; } if (inBlock) continue; newSource += line + Environment.NewLine;}var outputPath = "output/exp1.src";System.Console.WriteLine("去除注释完成,创建新文件。");using (var sWriter = new StreamWriter(outputPath)){ sWriter.Write(newSource);}System.Console.WriteLine("操作完成!文件路径:{0}", outputPath);结果测试源文件#include <iostream>#include <fstream>#include <iomanip>#include <cstdlib>using namespace std;int main(){ cout << '/'; ifstream ifile; //建立文件流对象 ofstream ofile; ifile.open("f:\\上机实验题\\C++\\ConsoleApplication2\\ConsoleApplication2\\源.cpp"); //打开F盘根目录下的fileIn.txt文件 ofile.open("f:\\上机实验题\\C++\\ConsoleApplication2\\ConsoleApplication2\\源.obj"); if (ifile.fail() || ofile.fail()) { //测试打开操作是否成功 cerr << "open file fail\n"; return EXIT_FAILURE; /*返回值EXIT_FAILURE(在cstdlib库中定义),用于向操作系统报* 告打开文件失败*/ } char ch; ch = ifile.get(); //进行读写操作 while (!ifile.eof()) { if (ch == 34) { //双引号中若出现“//”,双引号中的字符不消除 char temp = ch; //第一个双引号 ofile.put(ch); ch = ifile.get(); while (!ifile.eof()) { if (ch != temp) { //寻找下一个双引号 ofile.put(ch); ch = ifile.get(); } else { ofile.put(ch); break; } } ch = ifile.get(); continue; //双引号情况结束,重新新一轮判断 } if (ch == 47) { //出现第一个斜杠 char temp2 = ch; ch = ifile.get(); if (ch == 47) { //单行注释情况 ch = ifile.get(); while (!(ch == '\n')) ch = ifile.get(); } else if (ch == '*') { //多行注释情况 while (1) { ch = ifile.get(); while (!(ch == '*')) ch = ifile.get(); ch = ifile.get(); if (ch == 47) break; } ch = ifile.get(); } else { ofile.put(temp2); //temp2保存第一个斜杠,当上述两种情况都没有时,将此斜杠输出 } //ch = ifile.get(); } //cout << ch << endl; ofile.put(ch); //将字符写入文件流对象中 ch = ifile.get(); //从输入文件对象流中读取一个字符 } ifile.close(); //关闭文件 ofile.close(); cout << "/////*////ret/rtr////"; system("pause"); return 0;}处理后的结果#include <iostream>#include <fstream>#include <iomanip>#include <cstdlib>using namespace std;int main(){ cout << '/'; ifstream ifile; ofstream ofile; ifile.open("f:\\上机实验题\\C++\\ConsoleApplication2\\ConsoleApplication2\\源.cpp"); ofile.open("f:\\上机实验题\\C++\\ConsoleApplication2\\ConsoleApplication2\\源.obj"); if (ifile.fail() || ofile.fail()) { cerr << "open file fail\n"; return EXIT_FAILURE; } char ch; ch = ifile.get(); while (!ifile.eof()) { if (ch == 34) { char temp = ch; ofile.put(ch); ch = ifile.get(); while (!ifile.eof()) { if (ch != temp) { ofile.put(ch); ch = ifile.get(); } else { ofile.put(ch); break; } } ch = ifile.get(); continue; } if (ch == 47) { char temp2 = ch; ch = ifile.get(); if (ch == 47) { ch = ifile.get(); while (!(ch == '\n')) ch = ifile.get(); } else if (ch == '*') { while (1) { ch = ifile.get(); while (!(ch == '*')) ch = ifile.get(); ch = ifile.get(); if (ch == 47) break; } ch = ifile.get(); } else { ofile.put(temp2); } } ofile.put(ch); ch = ifile.get(); } ifile.close(); ofile.close(); cout << "/////*////ret/rtr////"; system("pause"); return 0;}完整代码https://github.com/Deali-Axy/CompilerConstructionLearning/blob/master/code/exp/exp1/Exp1.cs ...

May 11, 2019 · 3 min · jiezi

操作系统学习一NetCore-实现模拟多道程序设计的简单处理机调用

前言道程序设计中,经常是若干个进程同时处于就绪状态,为了使系统中的各进程有条不紊地运行,必须选择某种调度策略,以选择一个进程占用处理机。本次实验设计一个模拟单处理机调度的算法,以加深对处理机调度算法的理解。 要求按照时间片轮转算法设计模拟调度程序。输出进程的调度过程。思路分析由于本实验是按照处理机调度算法模拟实现处理机的调度,与真正的处理机调度过程并不完全相同,比如没有实现中断(时间片设为1),进程的运行也不是真正的运行,而是在屏幕上打印其运行时间等。所以要以文件的形式给出进程的信息,文件信息可参考如下: 进程ID 到达时间 估计运行时间 优先级0 0 3 21 2 6 42 4 4 03 6 5 34 8 2 1 以下是实验的大致思路: 建立三个队列:PCB队列,就绪队列,完成队列。PCB队列:保存将进入系统的进程。(由于没有实现中断,所以将进入系统运行的进程必须在程序运行前给出)。就绪队列:到达进程进入系统的时间,将该进程放入就绪队列,等待调度。完成队列:将“运行”完的进程放入完成队列。 进程运行过程是在屏幕上打印相关信息。使用轮转算法调度的进程应打印的信息包括:进程占用处理机序列,该进程每次占用处理机的开始时间与结束时间。 统计出进程的周转时间T和带权周转时间W。流程图 实现代码ProcessControlBlock.csusing System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace OperatingSystemExperiment.Exp1 { enum ProcessStatus { Ready, Run, Finish } /// <summary> /// 进程控制块 PCB /// </summary> class ProcessControlBlock { /// <summary> /// 进程号 /// </summary> public int ID; /// <summary> /// 进程状态 /// </summary> public ProcessStatus Status; /// <summary> /// 进程到达时间 /// </summary> public int ArriveTime; /// <summary> /// 估计运行时间 /// </summary> public int Time; /// <summary> /// 已运行时间 /// </summary> public int RunTime = 0; /// <summary> /// 等待时间 /// </summary> public int WaitTime; /// <summary> /// 优先级 /// </summary> public int Priority; /// <summary> /// 链接指针 /// </summary> public ProcessControlBlock Next; /// <summary> /// 开始时间 /// </summary> public int StartTime; /// <summary> /// 结束时间 /// </summary> public int FinishTime; public void Run() { this.Status = ProcessStatus.Run; if (RunTime >= Time) { this.Status = ProcessStatus.Finish; return; } this.RunTime++; } public void Wait() { this.WaitTime++; } public override string ToString() => String.Format("{0} {1} {2}", ID, StartTime, FinishTime); }}CentralProcessUnit.csusing System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.IO;namespace OperatingSystemExperiment.Exp1 { class CentralProcessUnit { private List<ProcessControlBlock> PCBList = new List<ProcessControlBlock>(); private Queue<ProcessControlBlock> FinishQueue = new Queue<ProcessControlBlock>(); private Queue<ProcessControlBlock> ReadyQueue = new Queue<ProcessControlBlock>(); public CentralProcessUnit() { LoadPcbList(); } /// <summary> /// 生成进程列表 /// </summary> /// <param name="count">进程数量</param> public static void GenerateProcessList(int count) { var processListFile = Path.Combine(Environment.CurrentDirectory, "process_list.txt"); var writer = new StreamWriter(processListFile); var rnd = new Random(DateTime.Now.Millisecond); for (var i = 0; i < count; i++) { var runTime = rnd.Next(1, 10); writer.WriteLine("{0} {1} {2} {3}", i, Math.Pow(2, i), runTime, rnd.Next(0, 4)); } writer.Close(); } /// <summary> /// 加载PCB列表 /// </summary> private void LoadPcbList() { var processListFile = Path.Combine(Environment.CurrentDirectory, "process_list.txt"); var reader = new StreamReader(processListFile); while (!reader.EndOfStream) { var line = reader.ReadLine(); var procInfo = line.Split(' '); PCBList.Add(new ProcessControlBlock { ID = int.Parse(procInfo[0]), ArriveTime = int.Parse(procInfo[1]), Time = int.Parse(procInfo[2]), Priority = int.Parse(procInfo[3]) }); } } /// <summary> /// CPU运行 /// </summary> public void Run() { var times = 0; while (true) { // 如果所有进程运行完,则退出循环 if (FinishQueue.Count == PCBList.Count) { break; } // 遍历所有进程列表 foreach (var p in PCBList) { // 根据进程到达时间判定是否有新进程加入,然后将进程状态设置为就绪 if (p.ArriveTime == times++) { Console.WriteLine("时间:{0},进程 {1} 到达", times, p.ID); p.Status = ProcessStatus.Ready; } // 讲就绪状态进程加入就绪列表 if (p.Status == ProcessStatus.Ready) {// Console.WriteLine("时间:{0},进程 {1} 加入就绪列表", times, p.ID); ReadyQueue.Enqueue(p); } // 如果就绪队列为空则进入下一次循环 if (ReadyQueue.Count == 0) {// Console.WriteLine("时间:{0},没有就绪进程,进入下一个循环", times); continue; } // 从就绪队列中取出一个进程运行 var currentProcess = ReadyQueue.Dequeue(); Console.WriteLine("时间:{0},运行进程 {1}", times, p.ID); currentProcess.Run(); // 将运行完毕进程加入完成列表 if (currentProcess.Status == ProcessStatus.Finish) { Console.WriteLine("时间:{0},进程 {1} 运行完毕,总运行时间:{2}", times, p.ID, p.RunTime); FinishQueue.Enqueue(currentProcess); } else currentProcess.Status = ProcessStatus.Ready; } } } }}Main.csnamespace OperatingSystemExperiment.Exp1{ public class Main { public static void Run() { CentralProcessUnit.GenerateProcessList(5); new CentralProcessUnit().Run(); } }}运行结果生成的process_list.txt内容:0 1 8 31 2 3 12 4 8 03 8 6 34 16 4 1控制台输出时间:1,运行进程 0时间:2,运行进程 1时间:3,运行进程 2时间:4,运行进程 3时间:5,运行进程 4时间:6,运行进程 0时间:7,运行进程 1时间:8,运行进程 2时间:9,进程 3 到达时间:9,运行进程 3时间:10,运行进程 4时间:11,运行进程 0时间:12,运行进程 1时间:13,运行进程 2时间:14,运行进程 3时间:15,运行进程 4时间:16,运行进程 0时间:17,运行进程 1时间:17,进程 1 运行完毕,总运行时间:3时间:18,运行进程 2时间:19,运行进程 3时间:20,运行进程 4时间:21,运行进程 0时间:23,运行进程 2时间:24,运行进程 3时间:25,运行进程 4时间:25,进程 4 运行完毕,总运行时间:4时间:26,运行进程 0时间:28,运行进程 2时间:29,运行进程 3时间:31,运行进程 0时间:33,运行进程 2时间:34,运行进程 3时间:34,进程 3 运行完毕,总运行时间:6时间:36,运行进程 0时间:38,运行进程 2时间:41,运行进程 0时间:41,进程 0 运行完毕,总运行时间:8时间:43,运行进程 2时间:43,进程 2 运行完毕,总运行时间:8 ...

May 11, 2019 · 3 min · jiezi

学习笔记Mopub在Unity环境下的集成Banner和Interstitial

首先,需要在项目中包含MoPubUnity.unitypackage,包的下载地址:https://github.com/mopub/mopu... 接下来点击菜单Assets->Import Package->Custom Package,选择包文件 至此包就被包含进去了,包里面有简单的使用用例,不过不适合直接接入项目中。我们还是要写一个新的Controller来处理广告相关的事务。 新的Controller在本文中被命名为 MopubAdsController,首先将广告id暴露到Inspector界面: #region Inspector Variables [SerializeField] private string iOSBannerID; [SerializeField] private string iOSInterstitialID; [SerializeField] private string iOSVideoID; [Space] [SerializeField] private string AndroidBannerID; [SerializeField] private string AndroidInterstitialID; [SerializeField] private string AndroidVideoID; #endregion由于广告请求的函数的参数不是string,而是string数组,所以需要另三个函数来承接上面的id private string[] _bannerAdUnits; private string[] _interstitialAdUnits; private string[] _rewardedVideoAdUnits;接下来进入Start函数: void Start() { #if UNITY_IOS _bannerAdUnits = new string[] {iOSBannerID}; _interstitialAdUnits = new string[] { iOSInterstitialID }; _rewardedVideoAdUnits = new string[] { iOSVideoID }; #elif UNITY_ANDROID || UNITY_EDITOR _bannerAdUnits = new string[] {AndroidBannerID}; _interstitialAdUnits = new string[] { AndroidInterstitialID }; _rewardedVideoAdUnits = new string[] { AndroidVideoID }; #endif var anyAdUnitId = _bannerAdUnits[0]; MoPub.InitializeSdk(new MoPub.SdkConfiguration { AdUnitId = anyAdUnitId, LogLevel = MoPubBase.LogLevel.MPLogLevelDebug, MediatedNetworks = new MoPub.MediatedNetwork[] { }, }); MoPub.LoadBannerPluginsForAdUnits(_bannerAdUnits); #if UNITY_IOS MoPub.CreateBanner(_bannerAdUnits[0], MoPubBase.AdPosition.BottomCenter); #elif UNITY_ANDROID MoPub.CreateBanner("REPLACE_BY_ANDROID_AD_UNIT_ID_HERE", MoPubAdPosition.BottomCenter ); #endif MoPub.LoadInterstitialPluginsForAdUnits(_interstitialAdUnits); MoPub.RequestInterstitialAd(_interstitialAdUnits[0]); MoPub.LoadRewardedVideoPluginsForAdUnits(_rewardedVideoAdUnits); //初始化各种广告,现在还没有使用好几个id的需求,所以每个广告类型先都用一个id,所以都取[0]。按着官方用例,如果有多种id的话可以写一个for循环直接遍历创造就好 //以下是各种回调的承接,相当于OC的XX.delegate = self; MoPubManager.OnSdkInitializedEvent += OnSdkInitializedEvent; MoPubManager.OnAdLoadedEvent += OnAdLoadedEvent; MoPubManager.OnAdFailedEvent += OnAdFailedEvent; MoPubManager.OnInterstitialLoadedEvent += OnInterstitialLoadedEvent; MoPubManager.OnInterstitialFailedEvent += OnInterstitialFailedEvent; MoPubManager.OnInterstitialDismissedEvent += OnInterstitialDismissedEvent; MoPubManager.OnRewardedVideoLoadedEvent += OnRewardedVideoLoadedEvent; MoPubManager.OnRewardedVideoFailedEvent += OnRewardedVideoFailedEvent; MoPubManager.OnRewardedVideoFailedToPlayEvent += OnRewardedVideoFailedToPlayEvent; MoPubManager.OnRewardedVideoClosedEvent += OnRewardedVideoClosedEvent; }其中: ...

May 10, 2019 · 2 min · jiezi

用C在去中心化交易所OceanOne上挂单买卖任意ERC20-token

用C#在去中心化交易所OceanOne上挂单买卖任意ERC20 token 在上一课中,我们介绍了如何在OceanOne交易比特币。OceanOne支持交易任何Mixin Network上的token,包括所有的ERC20和EOS token,不需要任何手续和费用,直接挂单即可。下面介绍如何将将一个ERC20 token挂上OceanOne交易。掌握了ERC20的代币买卖之后,你就可以用同样的方法买卖任何EOS以及其他Mixin Network上的token 此处我们用一个叫做Benz的ERC20 token为例。这个token已经被充值进Mixin Network,你可以在区块链浏览器看到这个token在Mixin Network内部的总数和交易 预备知识:先将Ben币存入你的钱包,然后使用getAssets API读取它的UUID. 取得该币的UUID调用 getAssets API 会返回json数据, 如: asset_id 币的UUID.public_key 该币的当前钱包的地址.symbol 币的名称. 如: Benz.if (cmd == "aw" ) { // Console.WriteLine(mixinApi.VerifyPIN(USRCONFIG.PinCode.ToString()).ToString()); MixinApi mixinApiNewUser = GetWalletSDK(); var assets = mixinApiNewUser.ReadAssets(); string wuuid = GetWalletUUID(); Console.WriteLine("Current wallet uuid is " + wuuid); foreach (Asset asset in assets) { if (asset.symbol == "EOS") { Console.WriteLine(asset.symbol + " Public Address is: " + asset.account_name + " " + asset.account_tag + " Balance is: " + asset.balance); } else Console.WriteLine(asset.symbol + " Public Address is: " + asset.public_key + " Balance is: " + asset.balance); Console.WriteLine(); }}调用 getAssets API的完整输出如下: ...

May 9, 2019 · 3 min · jiezi

通过-C-买卖Bitcoin

上一章介绍了Exincore,你可以1秒完成资产的市价买卖。如果你想限定价格买卖,或者买卖一些exincore不支持的资产,你需要OceanOne。 方案二: 在去中心化交易所OceanOne上挂单买卖BitcoinOcean.one是基于Mixin Network的去中心化交易所,它性能一流。你可以在OceanOne上交易任何资产,只需要将你的币转给OceanOne, 将交易信息写在交易的memo里,OceanOne会在市场里列出你的交易需求,交易成功后,会将目标币转入到你的MixinNetwork帐上,它有三大特点与优势: 不需要在OceanOne注册不需要存币到交易所支持所有Mixin Network上能够转账的资产,所有的ERC20 EOS代币。预备知识:你先需要创建一个机器人, 方法在 教程一. 安装依赖包我们需要依赖 MsgPack.Cli and mixin-csharp-sdk ,第四章 已经做过介绍, 你应该先安装过它了. 充币到 Mixin Network, 并读出它的余额.此处演示用 USDT购买BTC 或者 用BTC购买USDT。交易前,先检查一下钱包地址。完整的步骤如下: 检查比特币或USDT的余额,钱包地址。并记下钱包地址。从第三方交易所或者你的冷钱包中,将币充到上述钱包地址。再检查一下币的余额,看到帐与否。(比特币的到帐时间是5个区块的高度,约100分钟)。比特币与USDT的充值地址是一样的。 if (cmd == "aw" ) { // Console.WriteLine(mixinApi.VerifyPIN(USRCONFIG.PinCode.ToString()).ToString()); MixinApi mixinApiNewUser = GetWalletSDK(); var assets = mixinApiNewUser.ReadAssets(); string wuuid = GetWalletUUID(); Console.WriteLine("Current wallet uuid is " + wuuid); foreach (Asset asset in assets) { if (asset.symbol == "EOS") { Console.WriteLine(asset.symbol + " Public Address is: " + asset.account_name + " " + asset.account_tag + " Balance is: " + asset.balance); } else Console.WriteLine(asset.symbol + " Public Address is: " + asset.public_key + " Balance is: " + asset.balance); Console.WriteLine(); }}取得Ocean.one的市场价格信息如何来查询Ocean.one市场的价格信息呢?你要先了解你交易的基础币是什么,如果你想买比特币,卖出USDT,那么基础货币就是USDT;如果你想买USDT,卖出比特币,那么基础货币就是比特币. ...

May 7, 2019 · 4 min · jiezi

NET-Core教程给API加一个服务端缓存啦

.NET Core教程--给API加一个服务端缓存啦以前给API接口写缓存基本都是这样写代码: // redis key var bookRedisKey = ConstRedisKey.RecommendationBooks.CopyOne(bookId);// 获取缓存数据 var cacheBookIds = _redisService.ReadCache<List<string>>(bookRedisKey);if (cacheBookIds != null){ // return}else{ // 执行另外的逻辑获取数据, 然后写入缓存}然后把这一坨坨代码都散落在每个地方。 某一天,突然想起我这边的缓存基本时间都差不多,而且都是给Web API用的, 直接在API层支持缓存不就完事了。 所以, 这里用什么来做呢。 在.NET Core Web API这里的话, 两种思路:Middleware 或者ActionFilter. 不了解的同学可以看下面的文档: ASP.NET Core 中文文档 第四章 MVC(4.3)过滤器 ASP.NET Core 中文文档 第三章 原理(2)中间件 基于我这边只是部分接口支持缓存的话, 直接还是用ActionFilter实现就可以. 没撒说的, 直接上代码. using System;using Microsoft.AspNetCore.Http;using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Mvc.Filters;using Newtonsoft.Json.Linq;namespace XXXAPI.Filters{ public class DefaultCacheFilterAttribute : ActionFilterAttribute { // 这个时间用于给子类重写,实现不同时间级别的缓存 protected TimeSpan _expireTime; // redis读写的类,没撒看的 private readonly RedisService _redisService; public DefaultCacheFilterAttribute(RedisService redisService) { _redisService = redisService; } public override void OnActionExecuting(ActionExecutingContext context) { if (context.HttpContext.Request.Query.ContainsKey("refresh")) { return; } KeyConfig redisKey = GetRequestRedisKey(context.HttpContext); var redisCache = _redisService.ReadCache<JToken>(redisKey); if (redisCache != null) { context.Result = new ObjectResult(redisCache); } return; } public override void OnActionExecuted(ActionExecutedContext context) { KeyConfig redisKey = GetRequestRedisKey(context.HttpContext); var objResult = (ObjectResult)context.Result; if (objResult == null) { return; } var jToken = JToken.FromObject(objResult.Value); _redisService.WriteCache(redisKey, jToken); } private KeyConfig GetRequestRedisKey(HttpContext httpContext) { var requestPath = httpContext.Request.Path.Value; if (!string.IsNullOrEmpty(httpContext.Request.QueryString.Value)) { requestPath = requestPath + httpContext.Request.QueryString.Value; } if (httpContext.Request.Query.ContainsKey("refresh")) { if (httpContext.Request.Query.Count == 1) { requestPath = requestPath.Replace("?refresh=true", ""); } else { requestPath = requestPath.Replace("refresh=true", ""); } } // 这里也就一个redis key的类 var redisKey = ConstRedisKey.HTTPRequest.CopyOne(requestPath); if (_expireTime != default(TimeSpan)) { redisKey.ExpireTime = _expireTime; } return redisKey; } } public static class ConstRedisKey { public readonly static KeyConfig HTTPRequest = new KeyConfig() { Key = "lemon_req_", ExpireTime = new TimeSpan(TimeSpan.TicksPerMinute * 30), DBName = 5 }; } public class KeyConfig { public string Key { get; set; } public TimeSpan ExpireTime { get; set; } public int DBName { get; set; } public KeyConfig CopyOne(string state) { var one = new KeyConfig(); one.DBName = this.DBName; one.Key = !string.IsNullOrEmpty(this.Key) ? this.Key + state : state; one.ExpireTime = this.ExpireTime; return one; } }}然后使用的地方, 直接给Controller的Action方法加上注解即可. ...

May 6, 2019 · 2 min · jiezi

dotnetwarp-NSSM-部署-net-core-项目到-windows-服务

如果你想将 .net core 项目以服务的形式部署到 windows 系统,希望本篇文章能够让你少走弯路dotnet-warp 安装使用dotnet-warp 是一个全局的.NET Core 工具,允许将.NET Core 项目打包为单个可执行文件 项目地址:https://github.com/Hubert-Ryb... 安装:dotnet tool install --global dotnet-warp 使用:在项目输出目录执行 dotnet-warp 即可将应用打包成一个exe文件 (支持:win-x64,linux-x64,osx-x64) NSSM 安装使用NSSM 是一个服务封装程序,它可以方便的将 Exe 程序封装成 windows 服务运行下载:nssm-2.24使用:下载后将对应版本拷贝到目标机器,然后命令行执行:nssm install 即可唤出可视化界面 选择应用路径,有参数的添加参数,指定服务名称,指定执行用户,然后安装即可安装成功别忘记启动:nssm start 服务名称执行/运行 nssm 接口查看所有命令,根据所需执行对应的命令即可 简单的给 asp .net core项目传入监听端口public static IWebHostBuilder CreateWebHostBuilder(string[] args){ List<string> urls = new List<string>(); urls.Add("http://*:5454");//默认监听 // --urls http://*:6060,https://*:2333 var urlIndex = args.ToList().IndexOf("--urls"); if (urlIndex > -1 && args.Length > urlIndex + 1 && !string.IsNullOrEmpty(args[urlIndex + 1])) { urls.AddRange(args[urlIndex + 1].Split(',')); } return WebHost.CreateDefaultBuilder(args) .UseUrls(urls.ToArray()) .UseStartup<Startup>();}注意事项wwwroot 文件夹需要手动拷贝发布到 Windows7 时遇到的错误 ****/hostfxr.dll 找不到,下载对应系统版本的 Windows6.1-KB2533623 补丁后重启即可

April 29, 2019 · 1 min · jiezi

Unity-3D-的-Shader-运行时状态及渲染模式问题

Unity 中的 Shader 有四种渲染模式,分别是: ——Opaque(不透明)——Cutout(镂空)——Fade(隐现)——Transparent(透明)之前我遇到一个需求,需要给特定的游戏物体添加一个冰冻的效果,但是给我的 Shader 是一个完全冰冻的 Shader,而且无法通过参数调整它的颜色值。之后我的同事告诉我可以通过修改渲染模式更改物体本身的Alpha值实现,所以第一步我寻找了网上关于修改运行时 Shader 渲染模式的代码,如下。http://www.voidcn.com/article... 然后就是简单的代码操作,如下。 //获取目标身上的渲染组件 var renderer = GetComponentsInChildren<Renderer>(); // 建立一个冰冻数组 第二个数组元素是事先找好的冰冻特效 Material[] frostMatetials = new Material[2] { renderer.material, frostEffect }; //添加冰冻特效到人物身上的数组中 renderer.materials = frostMatetials; //实例化一个渲染模式变量 var fadeMode = SetMaterialRenderingMode.RenderingMode.Fade; //根据网上的方法更改你的 Shader 渲染模式为 Fade SetMaterialRenderingMode.SetMaterialRenderingModeMethod( renderer.materials[0],fadeMode);由于 Color 的 Alpha 值是只读的不能修改,但是 Color 是可以修改的,所以要获取目标物体 Color 的 RGB 值,然后实例一个 Color 用构造函数更改为你想要的 Alpha 值。如下: Color currentcolor03 = new Color { a = 0.8f, r = renderer.materials[0].color.r, g = renderer.materials[0].color.g, b = renderer.materials[0].color.b }; //替换目标 Color 属性就可以实现了其实这个方法不是很好,主要是因为所给的 Shader 不能修改属性值,后来我拿到了可以更改属性值的Shader,事情就变得简单了许多。 ...

April 29, 2019 · 1 min · jiezi

推荐一个接口文档自动生成工具Swagger

本文包括两个部分: webapi中使用swagger修改webapi的路由和默认参数WebApi中使用swagger新建一个webapi项目 项目打开之后,选择 引用,右键,管理NuGet程序包 浏览,搜索swagger,选择第一个swashbuckle,安装 安装好之后,右键项目,选择属性,生成,在下面的输出那里勾选:XML文档文件,如果没有自动填充好路径,需要自己填写一下,文件名可以自己取。 打开App_Start文件夹下的SwaggerConfig.cs文件,新增一个如下方法:private static string GetXmlCommentsPath(){ return System.String.Format(@"{0}\bin\WebApiDemo.xml", System.AppDomain.CurrentDomain.BaseDirectory);}其中WebApiDemo.xml这个文件名要和自己在前一步填写的文件名一致 搜索GetXmlCommentsPath,下面能搜到已经注释了,自己把注释放开,要是没搜到,就自己手动写一下c.IncludeXmlComments(GetXmlCommentsPath());注意要写在register方法里面 打开valuescontroller,自己写一些注释 运行项目,在根路径后面直接加swagger,就会自动跳转到文档,如:http://localhost:8970/swagger,能看到我们写的一些注释 修改webapi的路由和默认参数在实际应用中,完全使用webapi的restful风格的api设计是比较少见的,请求方式一般也只使用get请求和post请求,所以我们做一些修改,使用的是类似restful风格的api设计,修改一下webapi的路由配置 把valuescontroller做一些修改 /// <summary>/// ValuesController的注释/// </summary>public class ValuesController : ApiController{ /// <summary> /// 获取列表 /// </summary> /// <returns></returns> [HttpGet] public IEnumerable<string> GetList(int pageIndex, int pageSize, string search = "") { return new string[] { "value1", "value2" }; } /// <summary> /// 设置键值对 /// </summary> /// <param name="value"></param> [HttpPost] public string PostData([FromBody]string key, [FromBody]string value = "value") { return "{\"" + key + "\":\"" + value + "\"}"; }}重新运行,能看到文档变成了如下,必填的参数显示required,非必填的参数可以不用填,post请求的参数也显示在文档里 ...

April 28, 2019 · 1 min · jiezi

程序猿修仙之路算法之快速排序到底有多快

<img src="https://www.cnblogs.com/image...; width="100%" hegiht="20%" align=center /> 分治思想关于排序,江湖盛传有一种分治思想,能大幅度提高排序心法的性能。所谓分治,即:化大为小,分而治之。达到治小而治大的成效。多年来基于分治思想衍生出多种排序心法,然万变不离其宗!虽然江湖上算法内功繁多,但是好的算法小编认为必须符合以下几个条件,方能真正提高习练者实力。 时间复杂度(运行时间)在算法时间复杂度维度,我们主要对比较和交换的次数做对比,其他不交换元素的算法,主要会以访问数组的次数的维度做对比。其实有很多修炼者对于算法的时间复杂度有点模糊,分不清什么所谓的 O(n),O(nlogn),O(logn)...等,也许下图对一些人有一些更直观的认识。 空间复杂度(额外的内存使用)排序算法的额外内存开销和运行时间同等重要。 就算一个算法时间复杂度比较优秀,空间复杂度非常差,使用的额外内存非常大,菜菜认为它也算不上一个优秀的算法。结果的正确性这个指标是菜菜自己加上的,我始终认为一个优秀的算法最终得到的结果必须是正确的。就算一个算法拥有非常优秀的时间和空间复杂度,但是结果不正确,导致修炼者经脉逆转,走火入魔,又有什么意义呢?原理基本思想:选取一个元素作为分割点,通过遍历把小于分割点的元素放到分割点左边,把大于分割点的元素放到分割点元素右边。然后再按此方法对两部分数据分别排序,以此类推,直到分割的数组大小为1。 整个排序过程可以递归进行,以此达到整个数据变成有序序列。过程实现快速排序的方式有很多,其中以类似指针移动方式最为常见,为什么最常见呢?因为它的空间复杂度为O(1),也就是说是原地排序。 我们从待排序的记录序列中选取一个记录(通常第一个)作为基准元素(称为key)key=arr[left],然后设置两个变量,left指向数列的最左部,right指向数据的最右部。 key首先与arr[right]进行比较,如果arr[right]<key,则arr[left]=arr[right]将这个比key小的数放到左边去,如果arr[right]>key则我们只需要将right--,right--之后,再拿arr[right]与key进行比较,直到arr[right]<key交换元素为止。 如果右边存在arr[right]<key的情况,将arr[left]=arr[right],接下来,将转向left端,拿arr[left ]与key进行比较,如果arr[left]>key,则将arr[right]=arr[left],如果arr[left]<key,则只需要将left++,然后再进行arr[left]与key的比较。 然后再移动right重复上述步骤 最后得到 {23 58 13 10 57 62} 65 {106 78 95 85},再对左子数列与右子数列进行同样的操作。最终得到一个有序的数列。{23 58 13 10 57 62} 65 {106 78 95 85}{10 13} 23 {58 57 62} 65 {85 78 95} 10610 13 23 57 58 62 65 78 85 95 106性能特点关于复杂度相关O(n)等公式,我这里需要强调一点,公式代表的是算法的复杂度增长的趋势,而不是具体计算复杂度的公式。比如:O(n²)和O(n)相比较,只是说明 O(n²)增长的趋势要比o(n)快,并不是说明O(n²)的算法比O(n)的算法所用时间一定就要多。时间复杂度快速排序平均时间复杂度为O(nlogn),最好情况下为O(nlogn),最坏情况下O(n²) 空间复杂度基于以上例子来实现的快排,空间复杂度为O(1),也就是原地排序。 稳定性举个例子:待排序数组:int a[] ={1, 2, 2, 3, 4, 5, 6}; ...

April 27, 2019 · 2 min · jiezi

中西方-IT-界的思维冲突-没事儿瞎AA的

前言说下本人背景先,底层切图妞一枚,兜兜转转目前米国某不大出名城市当地大厂就业。能歌善舞,人缘好不小心跟公司的某技术大佬成了好朋友。每天上下班搭大佬便车,所以偶尔聊聊他遇到的问题。恰好今天他聊到了他跟印度分公司的技术们合作的问题,而我偶尔也看看某国内技术群里大家的讨论。觉得可以把我的所见所闻瞎AA一下。 中西方 IT 界思维冲突 西方:提倡技术共享,提高团队效率中方:提升个人不可替代性先说下我朋友目前面临的问题,他目前有个项目是有一部分工作交给了印度分公司的技术团队,所以每天都需要跟印度的技术团队进行沟通。他说印度技术团队的思维方式他非常不能理解,公司交给技术团队的任务,由于种种原因,他们并没有完全按照公司的需求和进度完成,然后总公司的团队就为了帮助他们把部分需求拆解开了做了规划并准备拿回总部团队开始做了。然后印度团队就因为这件事情非常地不开心,说是原本属于他们的任务被其他团队抢走了。 然后朋友就中间进行协调,首先印度团队的当前任务还有很多在排期,总部团队拿回去的部分印度团队排期根本就没有排到,两个团队同时进行并不会有冲突,为什么印度团队会突然哪么生气?当时我的第一反应是,他们是外包怕活儿做完了就会被炒鱿鱼吧。朋友说并不是这样的情况,他们是印度子公司,团队成员都是公司的正式员工。我的第二反应是,他们是不是觉得被拿走的部分是核心技术部分,为了保持自己团队在公司的重要作用,他们想把核心技术部分任务都保留在自己的团队,现在被拿走了,所以觉得很生气。经我这么一说,朋友才豁然开朗,他说还真有可能是这个原因。因为,印度团队总想接触公司不同的产品,可是在公司看来所有的产品都是核心技术部分,而且公司内部的技术共享氛围非常的浓,如果想技术领域有提升是完全可以通过公司内部的交流平台获取到信息的。接下来,我就说了说我在国内看到的一些现象,说了下我的看法。国内很多技术都讲究在团队中的不可替代性,而一个团队呢讲究在一个公司中的不可替代性。这个说法恐怕做为码农的都不陌生,很多时候国内技术群一聊就聊到大龄,码农生存指南之类的都会劝你不要做一些底层劳动,找准团队/公司的核心技术,掌握公司的核心技术,巴不得整个团队少了你就立马解散,公司少了你就分分钟垮掉。恐怕印度团队也是出于这样的考虑。朋友想了想说,这边的技术是不会这样做的,公司都是鼓励大家技术共享的,大家想的是如果你出于某种情况比如需要因私请个长假,团队是需要继续运行下去的,不会因为你一个人的问题,导致项目搁置。 题外话 听到国内技术群讨论说,80后程序员在国内已经是老程序员了,貌似食物链底端的哪种,如果还没有到管理层,就是马上要被劝退的一批了。 (此处重点信息:1)程序员年龄,2)对管理层的执着)米国这边还有年近40刚转行做初级程序员的。另外,美国公司的管理岗位只是一个岗位而已,愿意负担更多责任,善于与团队沟通的可以转做管理岗位。但是并不是每个技术的终点都需要走上管理岗,可以技术到退休。总结:没啥好总结的~~ 千人千篇总结

April 24, 2019 · 1 min · jiezi

走进c#

本周项目逐渐完工,没有发现什么重大问题,所以一直在写.net的实验,.net框架大部分使用的是以c#编写的,上课的时候也仔细听了一下,也算对c#有些了解,之前先学的java,对比java,感觉c#有其他的特性(也可能java也有,只是我不熟悉),在这里总结一下(本文只是个人的理解,如有错误,希望能指出并原谅,仅是初学).1.属性和字段之前我一直以为属性和字段是同一个意思,但其实不是这样的,字段通常是在类中定义的类成员变量,而属性是对字段的封装,供外部访问。这在java中就是像getXX(),setXX()的方法一样.例如://java中class Person { Stringname;// 字段(类成员) public void setName(String name) { //name的写属性 this.name = name; } public String getName() { //name的读属性 return this.name; }}c#的属性写法类似type.js的:class Person{ string name; public string Name{ get{ return name; } set{ name = value; } }}这里属性的定义就是: [修饰符] 类型说明符 Name,就是上面第三行,此时name就是字段,Name就是属性了,这个属性是公有的可直接获取,但是获取和赋值都在我们的控制中,也可以去掉get或set将字段设置为只可读或只可设置.2.多态在c#中,实现多态重写父类方法是,只能重写虚方法,需要使用到关键词virtual.例如:class Program { static void Main(string[] args) { Father father = new Child(); father.sayName(); } } class Father { public void sayName() { Console.WriteLine(“fathler”); } } class Child : Father { public void sayName() { Console.WriteLine(“child”); } }定义一个父类和子类,在入口函数Main中新建一个子类赋值给父类,根据多态的含义,在java中肯定直接输出child了,但是c#中却直接输出了father:原因就是因为要覆盖父类中的方法,父类方法必须要用virtual修饰,子类方法要用override修饰,而java中默认所有的方法都是虚方法,所以java就直接输出child的. class Program { static void Main(string[] args) { Father father = new Child(); father.sayName(); } } class Father { public virtual void sayName() { Console.WriteLine(“fathler”); } } class Child : Father { public override void sayName() { Console.WriteLine(“child”); } }这么想来之前c++也见过virtual这个关键字,解决了多继承的问题,但是当时没看明白,果然还是用过才能理解。3.结构体结构体与类类似,就是类是放在堆中的,结构体放在栈中,优点就是创建快,缺点就是不能全局引用。// 写法与类类似,就是将class改为struct 也可继承 实现接口public struct {string title;public string gettitle() { return title;}}总结我感觉c#就像是c++的改进版,更加的面向对象,同时还吸取了java,js等其他语言的特点融合起来的,感觉就是现在的各种语言都有很多相似的地方,大家互相借鉴学习并融合,变得越来越强大了,同时学好一门语言学其他的压力也就没这么大了,不像刚开始的学c语言那么费劲了. ...

April 20, 2019 · 1 min · jiezi

C# 十进制转二进制

转载请标明原文地址:https://segmentfault.com/a/11…十进制转二进制:正整数:除2取余倒序负整数:正整数取反+1int = Int32 = 32位有符号整数内存表示 8位一组 4组 4x8=32↓首位表示正负 0 = 正 1 = 负00000000 00000000 00000000 00000000最多可以表示2³² = 4294967296表示整数范围: -2147483648 ~ 2147483647 (负数比正数多1 因为0占了1个数字)正整数转二进制:除2取余倒序举例:5:5 ÷ 2 = 2 …… 12 ÷ 2 = 1 …… 01 ÷ 2 = 0 …… 1除2除到整数为0 把余数倒序排列 5 = 101负整数转二进制:正整数转二进制 取反 + 1举例:-5:00000000 00000000 00000000 00000101 // 5的二进制11111111 11111111 11111111 11111010 // 取反11111111 11111111 11111111 11111011 // +1??!!为什么负整数二进制要取反再加1?为什么不能直接用符号位+正整数二进制表示?因为计算机只有加法没有减法 减法也转成加法算 为了好计算 就这样表示了 详细原因可以看下面的文章:《为什么负数的反码在取反后还要加一》https://blog.csdn.net/delltdk…二进制转十进制:正数:2次幂相加负数:取反+1 二次幂相加在二进制数上 从右往左标上次方0、1、2、3……101²¹位数字x2的次方再相加 1 0 1 = 1 0 1 = 1011x2² + 0x2¹ + 1x2 = 4 + 0 + 1 = 5测试代码:using UnityEngine;using System;/// <summary>/// 十进制转二进制测试/// ZhangYu 2019-04-19/// </summary>public class ConvertTest : MonoBehaviour { private static void Test() { // 正整数 print(" 1 = " + Convert.ToString(1, 2)); // 1 = 00000000 00000000 00000000 00000001 print(" 2 = " + Convert.ToString(2, 2)); // 2 = 00000000 00000000 00000000 00000010 print(" 3 = " + Convert.ToString(3, 2)); // 3 = 00000000 00000000 00000000 00000011 print(" 4 = " + Convert.ToString(4, 2)); // 4 = 00000000 00000000 00000000 00000100 print(" 5 = " + Convert.ToString(5, 2)); // 5 = 00000000 00000000 00000000 00000101 // 负整数 print("-1 = " + Convert.ToString(-1, 2)); // -1 = 11111111 11111111 11111111 11111111 print("-2 = " + Convert.ToString(-2, 2)); // -2 = 11111111 11111111 11111111 11111110 print("-3 = " + Convert.ToString(-3, 2)); // -3 = 11111111 11111111 11111111 11111101 print("-4 = " + Convert.ToString(-4, 2)); // -4 = 11111111 11111111 11111111 11111100 print("-5 = " + Convert.ToString(-5, 2)); // -5 = 11111111 11111111 11111111 11111011 // 极值 print(int.MaxValue + “=” + Convert.ToString(int.MaxValue, 2)); // 2147483647 = 01111111 11111111 11111111 11111111 print(int.MinValue + “=” + Convert.ToString(int.MinValue, 2)); // -2147483648 = 10000000 00000000 00000000 00000000 }}转载请标明原文地址:https://segmentfault.com/a/11… ...

April 19, 2019 · 2 min · jiezi

ORM 开发环境之利器:MVC 中间件 FreeSql.AdminLTE

前言这是一篇纯技术干货的分享文章,FreeSql 已经基本完成 .NETCore 最方便的 ORM 使命,我们正在筹备生态的建立,比如 ABP 中如何使用 FreeSql 的实现,需要各种各样的扩展包,好多好多工作量。有没有大神愿意无偿参与做这件事情,好吧。。应该没有人!!大约是在三天前,因为使用 FreeSql 的某项目需要做一个简单的后台功能,以便录入或管理数据。在实施的过程中好怀念当初 dotnetGen 生成器的味道,用它产生 curd 基本功能几乎是秒做;然后今天发表的 FreeSql.AdminLTE 主角,已经实现了相关功能,它是怎么干这个事情的,且看下面内容;功能介绍它是 FreeSql 衍生出来的 .NETCore MVC 中间件扩展包,基于 AdminLTE 前端框架动态产生实体的增删查改界面;输入:实体1、实体2、实体3输出:后台管理的功能只需要传入实体,就可以形成 curd 的管理功能,是不是有些骚啊~~~先发一张运行后的图片尝个鲜:这是根据实体产生 curd 界面的 mvc 中间件,开发时预览数据好方便啊。看完预览图不由得再感叹一次 FreeSql 的易用性,那句口号:做 .NETCore 最方便的 ORM! 没有说错。。。作者多次提及:“我们是日式简约风格,没那么复杂的用法”,也验证了这一点。。添加/修改中件间产生的界面包括添加、修改数据的功能,普通实体的根据属性的类型与 Html5 UI 一一映射;比较特殊的映射规则:c# 类型Html5布尔复选框枚举下拉选择日期日期控件ManyToOne 导航属性下拉选择ManyToMany 导航属性多选器等等。。。什么情况会产生【上传文件】控件?有兴趣的可以了解源码,目前没有开放在外部配置。查询/过滤中件间为每个实体提供了分页列表查询,每页为20条数据;除此外,还提供了过滤条件的支持,规则是根据导航属性(ManyToOne、ManyToMany)。比如【文章实体】,内含有【分类id】+【分类对象】,则【文章】列表页会出现按【分类】筛选的UI,详见上面的 demo 示意图,或者下载对应的 demo 版本运行;删除中件间为每个实体提供了批量删除的功能;测试 demo我们习惯用 sqlite 做测试库,测试完毕直接删除目录,不留垃圾数据,所以下面的 demo 不需要修改任何地方,运行时自动建库、建表;提供 .net core 2.1、2.2 两种环境的测试 demo 下载:Demo for dotnet 2.1.zip、Demo for dotnet 2.2.zip第一步:dotnet restore第二步:dotnet run思考一番惊喜过后,你应该会考虑实用性,这样做有什么价值,可用于什么样的场景?这个扩展包简单的输入,产生巨量的功能反馈。目前来说它是死板的,对外提供的扩展性几乎为零,这样也就限定了它的应用场景。不合适的场景1、它不可替代我们自身开发的后台管理系统;2、它不适合摆放在公网正式环境,存在数据安全问题;3、欢迎补充。。。;谈谈定位目前的定位是这样的,在开发环境中使用,查阅预览实体数据,同时也比较方便的管理测试数据。一段拥有无比力量的小段代码,也是中间件界面的功能开启://可以配置子目录访问,如:/testadmin/app.UseFreeAdminLTE("/", typeof(Entities.Song), typeof(Entities.Tag));其他本次测试的实体有 versionRow 字段(乐观锁),当不修改内容时,点按钮后不会执行SQL。如何判定?可以回到列表,看 versionRow 的值没变化,如果执行了SQL,它的值会增加。不执行 SQL 有啥单独可说的?这就牵连到 FreeSql.DbContext 了,是它过滤了执行操作,有兴趣可移步了解;乐观锁FreeSql (乐观锁)说明:更新整个实体数据时,在并发情况下极容易造成旧数据将新的记录更新。行级锁的原理,是利用实体某字段,如:long version,更新前先查询数据,此时 version 为 1,更新时产生的 SQL 会附加 where version = 1,当修改失败时(即 Affrows == 0)抛出异常。每个实体只支持一个行级锁属性,在属性前标记特性:[Column(IsVersion = true)] 即可。适用 SetSource 更新,每次更新 version 的值都会增加 1收官FreeSql.AdminLTE 目前已经定版了,差不多已经把 dotnetGen 支持的功能都迁移了过来,完成了它应有的职责定位。下一个扩展包也非常有意思,欢迎持续关注我们,做 .NETCore 最方便的 ORM !(QQ群:4336577)github:https://github.com/2881099/FreeSqlhttps://github.com/2881099/FreeSql.AdminLTE ...

April 18, 2019 · 1 min · jiezi

在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)

本文首发于:码友网–一个专注.NET/.NET Core开发的编程爱好者社区。文章目录C#/.NET基于Topshelf创建Windows服务的系列文章目录:C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载 (1)在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务) (2)C#/.NET基于Topshelf创建Windows服务的守护程序作为服务启动的客户端桌面程序不显示UI界面的问题分析和解决方案 (3)前言在上一篇文章《C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载》中,我们了解发C#/.NET创建基于Topshelf Windows服务程序的大致流程,参数配置以及服务的安装和卸载。同时,我们也使用一个简单的定时任务演示了Topshelf服务的执行情况。今天我将继续为大家分享关于Topshelf主题的技术文章。本文主要演示在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)。创建一个演示应用程序首先,打开之前我们创建的[TopshelfDemoService.sln]解决方案。在这个解决方案中再创建一个名为TopshelfDemo.Client的客户端控制台应用程序,这个客户端程序即是我们需要使用[TopshelfDemoService]守护的。只是为了演示,所以客户端并没有实际意义的逻辑和功能,在Program.cs文件中,添加如下示例代码:using System;namespace TopshelfDemo.Client{ class Program { static void Main(string[] args) { Console.WriteLine(“这是一个由[码友网]创建的ERP系统示例程序,目前正在运行…”); Console.WriteLine(“技术支持:码友网(https://codedefautl.com) by Rector”); Console.ReadLine(); } }}仅此而已。编写好后,生成或者运行一下这个项目。你会看到一个控制台应用程序界面,如:实现守护程序功能再回到项目[TopshelfDemoService]中,打开类文件HealthMonitorService.cs,其中的定时功能演示的是一个检查某系统健康状况的任务,现在我们把定时任务功能改为守护某个或者某些应用程序。这里为了演示方便,没有重新创建服务类,在实际项目中,你也可以根据自己的情况创建不同的服务类。修改其中代码为如下所示:using System;using System.Collections.Generic;using System.Timers;namespace TopshelfDemoService{ internal class HealthMonitorService { /// <summary> /// 检测周期计时器 /// </summary> private readonly Timer _timer; /// <summary> /// 检测周期(秒) /// </summary> private int _monitorInterval = 10; /// <summary> /// 要守护的应用程序列表 /// </summary> private List<DaemonApplicationInfo> _daemonApps { get; set; } public HealthMonitorService() { // 初始化要守护的应用程序列表 // 实际项目中,你可以将这里的初始化参数放到配置文件/数据库/缓存中(怎么方便怎么来) _daemonApps = new List<DaemonApplicationInfo> { new DaemonApplicationInfo { ProcessName =“TopshelfDemo.Client”, // 请根据你的情况填写 AppDisplayName =“TopshelfDemo Client”, // 请根据你的情况填写 AppFilePath =@“D:\Projects\github\TopshelfDemoService\TopshelfDemo.Client\bin\Debug\TopshelfDemo.Client.exe” // 这里的路径请根据你的实际情况填写 } }; _timer = new Timer(_monitorInterval*1000) { AutoReset = true }; _timer.Elapsed += (sender, eventArgs) => Monitor(); } /// <summary> /// 守护应用程序的方法 /// </summary> private void Monitor() { foreach (var app in _daemonApps) { // 判断当前进程是存已启动 if (ProcessorHelper.IsProcessExists(app.ProcessName)) { Console.WriteLine(“Application[{0}] already exists.”, app.ProcessName); return; } try { // 当前主机进程列表中没有需要守护的进程名称,则启动这个进程对应的应用程序 ProcessorHelper.RunProcess(app.AppFilePath, app.Args); } catch (Exception ex) { Console.WriteLine(“Start application failed:{0}”, ex); } } } public void Start() { _timer.Start(); } public void Stop() { _timer.Stop(); } }}新建类DaemonApplicationInfo.cs和ProcessorHelper.cs,编写如下代码。DaemonApplicationInfo.cs(需守护的应用程序实体类):namespace TopshelfDemoService{ /// <summary> /// 需守护的应用程序实体 /// </summary> public class DaemonApplicationInfo { /// <summary> /// 进程中显示的名称 /// </summary> public string ProcessName { get; set; } /// <summary> /// 应用程序安装路径 /// </summary> public string AppFilePath { get; set; } /// <summary> /// 应用程序的名称 /// </summary> public string AppDisplayName { get; set; } /// <summary> /// 参数 /// </summary> public string Args { get; set; } }}ProcessorHelper.cs(进程处理帮助类):using System.Collections.Generic;using System.Diagnostics;using System.Linq;namespace TopshelfDemoService{ /// <summary> /// 进程处理帮助类 /// </summary> internal class ProcessorHelper { /// <summary> /// 获取当前计算机所有的进程列表(集合) /// </summary> /// <returns></returns> public static List<Process> GetProcessList() { return GetProcesses().ToList(); } /// <summary> /// 获取当前计算机所有的进程列表(数组) /// </summary> /// <returns></returns> public static Process[] GetProcesses() { var processList = Process.GetProcesses(); return processList; } /// <summary> /// 判断指定的进程是否存在 /// </summary> /// <param name=“processName”></param> /// <returns></returns> public static bool IsProcessExists(string processName) { return Process.GetProcessesByName(processName).Length > 0; } /// <summary> /// 启动一个指定路径的应用程序 /// </summary> /// <param name=“applicationPath”></param> /// <param name=“args”></param> public static void RunProcess(string applicationPath, string args = “”) { try { var psi = new ProcessStartInfo { FileName = applicationPath, WindowStyle = ProcessWindowStyle.Normal, Arguments = args }; Process.Start(psi); } catch{} } }}完成以上编码后,我们将项目程序[TopshelfDemo.Client]和[TopshelfDemoService]先都关闭掉(如果已运行),接着运行项目[TopshelfDemoService],下面就是见证奇迹的时刻啦:可以看到,守护程序[TopshelfDemoService]自动启动了客户端程序[TopshelfDemo.Client.exe],并且只会启动一个客户端实例程序。当我们把客户端关闭后,下次守护程序检测的时候客户端程序又会被重启。遗留问题如果你正高高兴兴地将TopshelfDemoService作为Windows服务安装,那么你可能会遇到这个问题,即守护进程正常运行,客户端程序也能正常地被守护并且启动,在Windows的"任务管理器"中也可以找到客户端的进程,但却看不到客户端程序的UI界面。这是怎么回事呢???是不是哪里出错了呢???应该如何解决呢???预知后事如何请听下回分解(未完待续)…好了,今天的在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)的分享就到这里。我是Rector,希望本文对C#/.NET开发的你有所帮助。源代码下载本示例代码托管地址可以在原出处找到:示例代码下载地址 ...

April 17, 2019 · 2 min · jiezi

程序猿修仙之路--算法之希尔排序

自冯诺依曼开启大计算机时代以来,经过近一个世纪的蓬勃发展,已然成为一个人才众多的群体:IT江湖。依附市场规律,江湖上悄然兴起数十宗门,其中以AI,大数据近期最为热门。每个宗门人才济济,抢夺人才大战早已在阿里,腾讯,百度等数百个国度白热化。IT江湖人士凭借JAVA,Python等武器,在精通各路内功心法的基础上在各个国度扬名立万,修仙成佛者众多,为后人树下追宠之榜样。内功心法众多,其中以算法最为精妙,是修仙德道必经之路。虽然江湖上算法内功繁多,但是好的算法小编认为必须符合以下几个条件,方能真正提高习练者实力。时间复杂度(运行时间)在算法时间复杂度维度,我们主要对比较和交换的次数做对比,其他不交换元素的算法,主要会以访问数组的次数的维度做对比。其实有很多修炼者对于算法的时间复杂度有点模糊,分不清什么所谓的 O(n),O(nlogn),O(logn)…等,也许下图对一些人有一些更直观的认识。空间复杂度(额外的内存使用)排序算法的额外内存开销和运行时间同等重要。 就算一个算法时间复杂度比较优秀,空间复杂度非常差,使用的额外内存非常大,菜菜认为它也算不上一个优秀的算法。结果的正确性这个指标是菜菜自己加上的,我始终认为一个优秀的算法最终得到的结果必须是正确的。就算一个算法拥有非常优秀的时间和空间复杂度,但是结果不正确,导致修炼者经脉逆转,走火入魔,又有什么意义呢?原理在上一篇我们修炼了插入排序,希尔排序(又名Shell’s Sort)本质上属于插入排序,是插入排序的一种更高效升级版本,也称为缩小增量排序。同时希尔排序在时间复杂度上也是突破O(n²)的第一批算法之一。你说厉不厉害?~~基本思想通过直接插入排序的修炼,我们知道直接插入排序是一种性能比较低的初级算法,对修炼者提升不是不大, 但是有一点优势那就是对于小型数组或者部分有序的数组非常高效,希尔排序就是基于这一点优势对直接插入排序进行了改良。换句话说直接插入排序低效的原因在于无序,无序的程度越高越低效。例如:最小的元素初始位置在数组的另一端,此元素要想到达正确位置,是需要一个一个位置前移,最终需要N-1次移动。如何改变这种状态正是希尔排序的突破口。希尔排序的思想是把数组下标按照一定的增量h分组,然后对每组进行直接插入排序。在进行排序时,如果h很大,我们就能将元素移动到很远的地方,为实现更小的h有序创造方便。然后增量h逐渐减小(每个分组的元素量增多),直到h为1整个数组划分为一组,排序结束。也许一张更直观的图比上千句话效果都好复杂度时间复杂度最坏时间复杂度依然为O(n²),一些经过优化的增量序列如Hibbard经过复杂证明可使得最坏时间复杂度为O(n^3/2),最好情况下为O(n)属于线性复杂度。空间复杂度优于希尔排序本质上属于插入排序升级版,所以空间上和直接插入排序一致为O(1),在常数级别。性能和特点希尔排序之所以高效是因为它权衡了子数组的规模和有序性。排序之初各个子数组都很短,这种情况很适合插入排序。对于增量h的选择对希尔排序非常重要,直接影响其性能。其实除了h的选择之外,h之间的数学性质也影响希尔排序的性能,比如它们的公因子等。很多论文研究了各种不同的递增序列,但都无法证明某个序列是最好的。对于某些基础递增的序列其实在性能上和某些复杂的序列接近,所以很多情况下我们没有必要花大力气在复杂序列上的研究上。适用场景与插入排序不同,希尔排序可以适用于大型数组,它对任意排序的数组表现良好,虽然不是最好。实验证明,希尔排序比我们上两章学习的选择排序和插入排序要快的多,并且数组越大,优势越大。目前最重要的结论是:希尔排序的运行时间达不到平方级别。对于中等大小的数组希尔排序的时间是在可接受范围之内的,因为它的代码量很小,而且需要的额外空间很小,几乎可以忽略。对于其他更高效的其他算法,可能比希尔排序更高效,但是代码也更复杂,性能上比希尔排序也高不了几倍,所以在很多情况下希尔排序成为首选的算法。其他直接插入排序是稳定的,希尔排序呢?由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序排序是不稳定的。试炼一发吧c# 武器版 static void Main(string[] args) { List<int> data = new List<int>() ; for (int i = 0; i < 11; i++) { data.Add(new Random(Guid.NewGuid().GetHashCode()).Next(1, 100)); } //打印原始数组值 Console.WriteLine($“原始数据: {string.Join(”,", data)}"); int n = data.Count; int h = 1; //计算初始化增量,网络提供,据说比较好的递增因子 while (h < n / 3) { h = 3 * h + 1; } Console.WriteLine($“初始化增量:{h}”); while (h >= 1) { for (int i = h; i < n; i++) { for (int j = i; j >=h&&data[j]<data[j-h]; j-=h) { //异或法 交换两个变量,不用临时变量 data[j] = data[j] ^ data[j - 1]; data[j - 1] = data[j] ^ data[j - 1]; data[j] = data[j] ^ data[j - 1]; } } h = h / 3; } //打印排序后的数组 Console.WriteLine($“排序数据: {string.Join(”,", data)}"); Console.Read(); }运行结果:原始数据: 47,50,32,42,44,79,10,16,51,74,52初始化增量:4排序数据: 10,16,32,42,44,47,50,51,52,74,79Golang 武器版package mainimport ( “fmt” “math/rand”)func main() { var data []int for i := 0; i < 11; i++ { data = append(data, rand.Intn(100)) } fmt.Println(data) var n = len(data) var h = 1 for h < n/3 { h = 3*h + 1 } fmt.Println(h) for h >= 1 { for i := h; i < n; i++ { for j := i; j >= h && data[j] < data[j-h]; j -= h { data[j], data[j-h] = data[j-h], data[j] } } h = h / 3 } fmt.Println(data)}运行结果:[81 87 47 59 81 18 25 40 56 0 94]4[0 18 25 40 47 56 59 81 81 87 94]添加关注,查看更精美版本,收获更多精彩 ...

April 15, 2019 · 2 min · jiezi

Unity C# for和foreach效率比较

先说测试结果:效率几乎完全一样,不用特意改变写法,喜欢哪种用哪种。测试代码:using UnityEngine;using UnityEditor;using System.Diagnostics;/// <summary>/// 执行时间测试/// ZhangYu 2019-04-13/// </summary>public class TimeTest : MonoBehaviour { private static Stopwatch watch; private void Start() { Execute(); } [MenuItem(“CONTEXT/TimeTest/执行”)] private static void Execute() { watch = new Stopwatch(); // 数据长度 int total = 100000000; int[] array = new int[total]; for (int i = 0; i < total; i++) { array[i] = i + 1; } // Foreach watch.Reset(); watch.Start(); foreachTest(array); watch.Stop(); string msgForeach = string.Format(“Foreach: {0}s”, watch.Elapsed); // For1 watch.Reset(); watch.Start(); foreachTest(array); watch.Stop(); string msgFor1 = string.Format(“For1: {0}s”, watch.Elapsed); // For2 watch.Reset(); watch.Start(); foreachTest(array); watch.Stop(); string msgFor2 = string.Format(“For2: {0}s”, watch.Elapsed); print(msgForeach); print(msgFor1); print(msgFor2); } // (1)0.7035506s // (2)0.7174406s // (3)0.7001000s // (4)0.7012998s // (5)0.7009337s public static void foreachTest(int[] array) { foreach (int item in array) { } } // (1)0.7014426s // (2)0.7172180s // (3)0.6987379s // (4)0.6987784s // (5)0.7051741s public static void forTest1(int[] array) { for (int i = 0; i < array.Length; i++) { } } // (1)0.7006860s // (2)0.7160505s // (3)0.6997564s // (4)0.7024032s // (5)0.7004985s public static void forTest2(int[] array) { int length = array.Length; for (int i = 0; i < length; i++) { } }}测试结果:排除运行环境的误差,for循环和foreach循环在数十次的测试结果中,效率基本是完全一样的,For2方法优化了一下length的取值,但没什么明显的性能差距,所以for和foreach喜欢那种用哪种吧,不用为了提高效率特意改变写法或习惯。 ...

April 13, 2019 · 1 min · jiezi