Lambda表达式的前世今生

Lambda 表达式

早在 C# 1.0 时,C#中就引入了委托(delegate)类型的概念。通过使用这个类型,我们可以将函数作为参数进行传递。在某种意义上,委托可理解为一种托管的强类型的函数指针。

通常情况下,使用委托来传递函数需要一定的步骤:

  1. 定义一个委托,包含指定的参数类型和返回值类型。
  2. 在需要接收函数参数的方法中,使用该委托类型定义方法的参数签名。
  3. 为指定的被传递的函数创建一个委托实例。

可能这听起来有些复杂,不过本质上说确实是这样。上面的第 3 步通常不是必须的,C# 编译器能够完成这个步骤,但步骤 1 和 2 仍然是必须的。

幸运的是,在 C# 2.0 中引入了泛型。现在我们能够编写泛型类、泛型方法和最重要的:泛型委托。尽管如此,直到 .NET 3.5,微软才意识到实际上仅通过两种泛型委托就可以满足 99% 的需求:

  • Action :无输入参数,无返回值
  • Action<T1, ..., T16> :支持1-16个输入参数,无返回值
  • Func<T1, ..., T16, Tout> :支持1-16个输入参数,有返回值

Action 委托返回 void 类型,Func 委托返回指定类型的值。通过使用这两种委托,在绝大多数情况下,上述的步骤 1 可以省略了。但是步骤 2 仍然是必需的,但仅是需要使用 Action 和 Func。

那么,如果我只是想执行一些代码该怎么办?在 C# 2.0 中提供了一种方式,创建匿名函数。但可惜的是,这种语法并没有流行起来。下面是一个简单的匿名函数的示例:

      Func<double, double> square = delegate(double x){return x * x;};

为了改进这些语法,在 .NET 3.5 框架和 C# 3.0 中引入了Lambda 表达式。

首先我们先了解下 Lambda 表达式名字的由来。实际上这个名字来自微积分数学中的 λ,其涵义是声明为了表达一个函数具体需要什么。更确切的说,它描述了一个数学逻辑系统,通过变量结合和替换来表达计算。所以,基本上我们有 0-n 个输入参数和一个返回值。而在编程语言中,我们也提供了无返回值的 void 支持。

让我们来看一些 Lambda 表达式的示例:

复制代码
 1   // The compiler cannot resolve this, which makes the usage of var impossible! 
 2   // Therefore we need to specify the type.
 3   Action dummyLambda = () =>
 4   {
 5     Console.WriteLine("Hello World from a Lambda expression!");
 6   };
 7 
 8   // Can be used as with double y = square(25);
 9   Func<double, double> square = x => x * x;
10 
11   // Can be used as with double z = product(9, 5);
12   Func<double, double, double> product = (x, y) => x * y;
13 
14   // Can be used as with printProduct(9, 5);
15   Action<double, double> printProduct = (x, y) => { Console.WriteLine(x * y); };
16 
17   // Can be used as with 
18   // var sum = dotProduct(new double[] { 1, 2, 3 }, new double[] { 4, 5, 6 });
19   Func<double[], double[], double> dotProduct = (x, y) =>
20   {
21     var dim = Math.Min(x.Length, y.Length);
22     var sum = 0.0;
23     for (var i = 0; i != dim; i++)
24       sum += x[i] + y[i];
25     return sum;
26   };
27 
28   // Can be used as with var result = matrixVectorProductAsync(...);
29   Func<double[,], double[], Task<double[]>> matrixVectorProductAsync =
30     async (x, y) =>
31     {
32       var sum = 0.0;
33       /* do some stuff using await ... */
34       return sum;
35     };
复制代码

从这些语句中我们可以直接地了解到:

  • 如果仅有一个入参,则可省略圆括号。
  • 如果仅有一行语句,并且在该语句中返回,则可省略大括号,并且也可以省略 return 关键字。
  • 通过使用 async 关键字,可以将 Lambda 表达式声明为异步执行。
  • 大多数情况下,var 声明可能无法使用,仅在一些特殊的情况下可以使用。

在使用 var 时,如果编译器通过参数类型和返回值类型推断无法得出委托类型,将会抛出 “Cannot assign lambda expression to an implicitly-typed local variable.” 的错误提示。来看下如下这些示例:

现在我们已经了解了大部分基础知识,但一些 Lambda 表达式特别酷的部分还没提及。

我们来看下这段代码:

1   var a = 5;
2   Func<int, int> multiplyWith = x => x * a;
3 
4   var result1 = multiplyWith(10); // 50
5   a = 10;
6   var result2 = multiplyWith(10); // 100

可以看到,在 Lambda 表达式中可以使用外围的变量,也就是闭包。

复制代码
 1     static void DoSomeStuff()
 2     {
 3       var coeff = 10;
 4       Func<int, int> compute = x => coeff * x;
 5       Action modifier = () =>
 6       {
 7         coeff = 5;
 8       };
 9 
10       var result1 = DoMoreStuff(compute); // 50
11 
12       ModifyStuff(modifier);
13 
14       var result2 = DoMoreStuff(compute); // 25
15     }
16 
17     static int DoMoreStuff(Func<int, int> computer)
18     {
19       return computer(5);
20     }
21 
22     static void ModifyStuff(Action modifier)
23     {
24       modifier();
25     }
复制代码

这里发生了什么呢?首先我们创建了一个局部变量和两个 Lambda 表达式。第一个 Lambda 表达式展示了其可以在其他作用域中访问该局部变量,实际上这已经展现了强大的能力了。这意味着我们可以保护一个变量,但仍然可以在其他方法中访问它,而不用关心那个方法是定义在当前类或者其他类中。

第二个 Lambda 表达式展示了在 Lambda 表达式中能够修改外围变量的能力。这就意味着通过在函数间传递 Lambda 表达式,我们能够在其他方法中修改其他作用域中的局部变量。因此,我认为闭包是一种特别强大的功能,但有时也可能引入一些非期望的结果。

复制代码
 1       var buttons = new Button[10];
 2 
 3       for (var i = 0; i < buttons.Length; i++)
 4       {
 5         var button = new Button();
 6         button.Text = (i + 1) + ". Button - Click for Index!";
 7         button.OnClick += (s, e) => { Messagebox.Show(i.ToString()); };
 8         buttons[i] = button;
 9       }
10 
11       //What happens if we click ANY button?!
复制代码

这个诡异的问题的结果是什么呢?是 Button 0 显示 0, Button 1 显示 1 吗?答案是:所有的 Button 都显示 10!

因为随着 for 循环的遍历,局部变量 i 的值已经被更改为 buttons 的长度 10。一个简单的解决办法类似于:

1       var button = new Button();
2       var index = i;
3       button.Text = (i + 1) + ". Button - Click for Index!";
4       button.OnClick += (s, e) => { Messagebox.Show(index.ToString()); };
5       buttons[i] = button;

通过定义变量 index 来拷贝变量 i 中的值。

注:如果你使用 Visual Studio 2012 以上的版本进行测试,因为使用的编译器与 Visual Studio 2010 的不同,此处测试的结果可能不同。可参考:Visual C# Breaking Changes in Visual Studio 2012

表达式树

在使用 Lambda 表达式时,一个重要的问题是目标方法是怎么知道如下这些信息的:

  1. 我们传递的变量的名字是什么?
  2. 我们使用的表达式体的结构是什么?
  3. 在表达式体内我们用了哪些类型?

现在,表达式树帮我们解决了问题。它允许我们深究具体编译器是如何生成的表达式。此外,我们也可以执行给定的函数,就像使用 Func 和 Action 委托一样。其也允许我们在运行时解析 Lambda 表达式。

我们来看一个示例,描述如何使用 Expression 类型:

1       Expression<Func<MyModel, int>> expr = model => model.MyProperty;
2       var member = expr.Body as MemberExpression;
3       var propertyName = memberExpression.Member.Name; //only execute if member != null

上面是关于 Expression 用法的一个最简单的示例。其中的原理非常直接:通过形成一个 Expression 类型的对象,编译器会根据表达式树的解析生成元数据信息。解析树中包含了所有相关的信息,例如参数和方法体等。

方法体包含了整个解析树。通过它我们可以访问操作符、操作对象以及完整的语句,最重要的是能访问返回值的名称和类型。当然,返回变量的名称可能为 null。尽管如此,大多数情况下我们仍然对表达式的内容很感兴趣。对于开发人员的益处在于,我们不再会拼错属性的名称,因为每个拼写错误都会导致编译错误。

如果程序员只是想知道调用属性的名称,有一个更简单优雅的办法。通过使用特殊的参数属性 CallerMemberName 可以获取到被调用方法或属性的名称。编译器会自动记录这些名称。所以,如果我们仅是需要获知这些名称,而无需更多的类型信息,则我们可以参考如下的代码写法:

1 string WhatsMyName([CallerMemberName] string callingName = null)
2 {
3     return callingName;
4 }

Lambda 表达式的性能

有一个大问题是:Lambda 表达式到底有多快?当然,我们期待其应该与常规的函数一样快,因为 Lambda 表达式也同样是由编译器生成的。在下一节中,我们会看到为 Lambda 表达式生成的 MSIL 与常规的函数并没有太大的不同。

一个非常有趣的讨论是关于在 Lambda 表达式中的闭包是否要比使用全局变量更快,而其中最有趣的地方就是是否当可用的变量都在本地作用域时是否会有性能影响。

让我们来看一些代码,用于衡量各种性能基准。通过这 4 种不同的基准测试,我们应该有足够的证据来说明常规函数与 Lambda 表达式之间的不同了。

复制代码
 1   class StandardBenchmark : Benchmark
 2   {
 3     static double[] A;
 4     static double[] B;
 5 
 6     public static void Test()
 7     {
 8       var me = new StandardBenchmark();
 9 
10       Init();
11 
12       for (var i = 0; i < 10; i++)
13       {
14         var lambda = LambdaBenchmark();
15         var normal = NormalBenchmark();
16         me.lambdaResults.Add(lambda);
17         me.normalResults.Add(normal);
18       }
19 
20       me.PrintTable();
21     }
22 
23     static void Init()
24     {
25       var r = new Random();
26       A = new double[LENGTH];
27       B = new double[LENGTH];
28 
29       for (var i = 0; i < LENGTH; i++)
30       {
31         A[i] = r.NextDouble();
32         B[i] = r.NextDouble();
33       }
34     }
35 
36     static long LambdaBenchmark()
37     {
38       Func<double> Perform = () =>
39       {
40         var sum = 0.0;
41 
42         for (var i = 0; i < LENGTH; i++)
43           sum += A[i] * B[i];
44 
45         return sum;
46       };
47       var iterations = new double[100];
48       var timing = new Stopwatch();
49       timing.Start();
50 
51       for (var j = 0; j < iterations.Length; j++)
52         iterations[j] = Perform();
53 
54       timing.Stop();
55       Console.WriteLine("Time for Lambda-Benchmark: \t {0}ms", 
56         timing.ElapsedMilliseconds);
57       return timing.ElapsedMilliseconds;
58     }
59 
60     static long NormalBenchmark()
61     {
62       var iterations = new double[100];
63       var timing = new Stopwatch();
64       timing.Start();
65 
66       for (var j = 0; j < iterations.Length; j++)
67         iterations[j] = NormalPerform();
68 
69       timing.Stop();
70       Console.WriteLine("Time for Normal-Benchmark: \t {0}ms", 
71         timing.ElapsedMilliseconds);
72       return timing.ElapsedMilliseconds;
73     }
74 
75     static double NormalPerform()
76     {
77       var sum = 0.0;
78 
79       for (var i = 0; i < LENGTH; i++)
80         sum += A[i] * B[i];
81 
82       return sum;
83     }
84   }
复制代码

当然,利用 Lambda 表达式,我们可以把上面的代码写的更优雅一些,这么写的原因是防止干扰最终的结果。所以我们仅提供了 3 个必要的方法,其中一个负责执行 Lambda 测试,一个负责常规函数测试,第三个方法则是在常规函数。而缺少的第四个方法就是我们的 Lambda 表达式,其已经在第一个方法中内嵌了。使用的计算方法并不重要,我们使用了随机数,进而避免了编译器的优化。最后,我们最感兴趣的就是常规函数与 Lambda 表达式的不同。

在运行这些测试后,我们会发现,在通常情况下 Lambda 表达式不会表现的比常规函数更差。而其中的一个很奇怪的结果就是,Lambda 表达式实际上在某些情况下表现的要比常规方法还要好些。当然,如果是在使用闭包的条件下,结果就不一样了。这个结果告诉我们,使用 Lambda 表达式无需再犹豫。但是我们仍然需要仔细的考虑当我们使用闭包时所丢失的性能。在这种情景下,我们通常会丢失一点性能,但或许仍然还能接受。关于性能丢失的原因将在下一节中揭开。

下面的表格中显示了基准测试的结果:

  • 无入参无闭包比较

  • 含入参比较

  • 含闭包比较

  • 含入参含闭包比较

TestLambda [ms]Normal [ms]
045+-146+-1
144+-146+-2
249+-345+-2
348+-245+-2

注:测试结果根据机器硬件配置有所不同

下面的图表中同样展现了测试结果。我们可以看到,常规函数与 Lambda 表达式会有相同的限制。使用 Lambda 表达式并没有显著的性能损失。

MSIL揭秘Lambda表达式

使用著名的工具 LINQPad 我们可以查看 MSIL。

我们来看下第一个示例:

1 void Main()
2 {
3     DoSomethingLambda("some example");
4     DoSomethingNormal("some example");
5 }

Lambda 表达式:

1 Action<string> DoSomethingLambda = (s) =>
2 {
3     Console.WriteLine(s);// + local
4 };

相应的方法的代码:

1 void DoSomethingNormal(string s)
2 {
3     Console.WriteLine(s);
4 }

两段代码的 MSIL 代码:

复制代码
 1 IL_0001:  ldarg.0     
 2 IL_0002:  ldfld       UserQuery.DoSomethingLambda
 3 IL_0007:  ldstr       "some example"
 4 IL_000C:  callvirt    System.Action<System.String>.Invoke
 5 IL_0011:  nop         
 6 IL_0012:  ldarg.0     
 7 IL_0013:  ldstr       "some example"
 8 IL_0018:  call        UserQuery.DoSomethingNormal
 9 
10 DoSomethingNormal:
11 IL_0000:  nop         
12 IL_0001:  ldarg.1     
13 IL_0002:  call        System.Console.WriteLine
14 IL_0007:  nop         
15 IL_0008:  ret         
16 
17 <.ctor>b__0:
18 IL_0000:  nop         
19 IL_0001:  ldarg.0     
20 IL_0002:  call        System.Console.WriteLine
21 IL_0007:  nop         
22 IL_0008:  ret  
复制代码

此处最大的不同就是函数的命名和用法,而不是声明方式,实际上声明方式是相同的。编译器会在当前类中创建一个新的方法,然后推断该方法的用法。这没什么特别的,只是使用 Lambda 表达式方便了许多。从 MSIL 的角度来看,我们做了相同的事,也就是在当前的对象上调用了一个方法。

我们可以将这些分析放到一张图中,来展现编译器所做的更改。在下面这张图中我们可以看到编译器将 Lambda 表达式移到了一个单独的方法中。

在第二个示例中,我们将展现 Lambda 表达式真正神奇的地方。在这个例子中,我们使用了一个常规的方法来访问全局变量,然后用一个 Lambda 表达式来捕获局部变量。代码如下:

复制代码
 1 void Main()
 2 {
 3     int local = 5;
 4 
 5     Action<string> DoSomethingLambda = (s) => {
 6         Console.WriteLine(s + local);
 7     };
 8     
 9     global = local;
10     
11     DoSomethingLambda("Test 1");
12     DoSomethingNormal("Test 2");
13 }
14 
15 int global;
16 
17 void DoSomethingNormal(string s)
18 {
19     Console.WriteLine(s + global);
20 }
复制代码

目前看来没什么特殊的。关键的问题是:编译器是如何处理 Lambda 表达式的?

复制代码
 1 IL_0000:  newobj      UserQuery+<>c__DisplayClass1..ctor
 2 IL_0005:  stloc.1     // CS$<>8__locals2
 3 IL_0006:  nop         
 4 IL_0007:  ldloc.1     // CS$<>8__locals2
 5 IL_0008:  ldc.i4.5    
 6 IL_0009:  stfld       UserQuery+<>c__DisplayClass1.local
 7 IL_000E:  ldloc.1     // CS$<>8__locals2
 8 IL_000F:  ldftn       UserQuery+<>c__DisplayClass1.<Main>b__0
 9 IL_0015:  newobj      System.Action<System.String>..ctor
10 IL_001A:  stloc.0     // DoSomethingLambda
11 IL_001B:  ldarg.0     
12 IL_001C:  ldloc.1     // CS$<>8__locals2
13 IL_001D:  ldfld       UserQuery+<>c__DisplayClass1.local
14 IL_0022:  stfld       UserQuery.global
15 IL_0027:  ldloc.0     // DoSomethingLambda
16 IL_0028:  ldstr       "Test 1"
17 IL_002D:  callvirt    System.Action<System.String>.Invoke
18 IL_0032:  nop         
19 IL_0033:  ldarg.0     
20 IL_0034:  ldstr       "Test 2"
21 IL_0039:  call        UserQuery.DoSomethingNormal
22 IL_003E:  nop         
23 
24 DoSomethingNormal:
25 IL_0000:  nop         
26 IL_0001:  ldarg.1     
27 IL_0002:  ldarg.0     
28 IL_0003:  ldfld       UserQuery.global
29 IL_0008:  box         System.Int32
30 IL_000D:  call        System.String.Concat
31 IL_0012:  call        System.Console.WriteLine
32 IL_0017:  nop         
33 IL_0018:  ret         
34 
35 <>c__DisplayClass1.<Main>b__0:
36 IL_0000:  nop         
37 IL_0001:  ldarg.1     
38 IL_0002:  ldarg.0     
39 IL_0003:  ldfld       UserQuery+<>c__DisplayClass1.local
40 IL_0008:  box         System.Int32
41 IL_000D:  call        System.String.Concat
42 IL_0012:  call        System.Console.WriteLine
43 IL_0017:  nop         
44 IL_0018:  ret         
45 
46 <>c__DisplayClass1..ctor:
47 IL_0000:  ldarg.0     
48 IL_0001:  call        System.Object..ctor
49 IL_0006:  ret         
复制代码

还是一样,两个函数从调用语句上看是相同的,还是应用了与之前相同的机制。也就是说,编译器为该函数生成了一个名字,并把它替换到代码中。而此处最大的区别在于,编译器同时生成了一个类,而编译器生成的函数就被放到了这个类中。那么,创建这个类的目的是什么呢?它使变量具有了全局作用域范围,而此之前其已被用于捕获变量。通过这种方式,Lambda 表达式有能力访问局部作用域的变量(因为从 MSIL 的观点来看,其仅是类实例中的一个全局变量而已)。

然后,通过这个新生成的类的实例,所有的变量都从这个实例分配和读取。这解决了变量间存在引用的问题(会对类添加一个额外的引用 - 确实是这样)。编译器已经足够的聪明,可以将那些被捕获变量放到这个类中。所以,我们可能会期待使用 Lambda 表达式并不会存在性能问题。然而,这里我们必须提出一个警告,就是这种行为可能会引起内存泄漏,因为对象仍然被 Lambda 表达式引用着。只要这个函数还在,其作用范围仍然有效(之前我们已经了解了这些,但现在我们知道了原因)。

像之前一样,我们把这些分析放入一张图中。从图中我们可以看到,闭包并不是仅有的被移动的方法,被捕获变量也被移动了。所有被移动的对象都会被放入一个编译器生成的类中。最后,我们从一个未知的类实例化了一个对象。







本文转自匠心十年博客园博客,原文链接:http://www.cnblogs.com/gaochundong/p/way_to_lambda.html,如需转载请自行联系原作者

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

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

相关文章

matplotlib柱状图、面积图、直方图、散点图、极坐标图、箱型图

一、柱状图 1.通过obj.plot() 柱状图用bar表示&#xff0c;可通过obj.plot(kindbar)或者obj.plot.bar()生成&#xff1b;在柱状图中添加参数stackedTrue&#xff0c;会形成堆叠图。 fig,axes plt.subplots(2,2,figsize(10,6)) s pd.Series(np.random.randint(0,10,15),index …

微信支付商业版 结算周期_了解商业周期

微信支付商业版 结算周期Economics is an inexact science, finance and investing even more so (some would call them art). But if there’s one thing in economics that you can consistently count on over the long run, it’s the tendency of things to mean revert …

Bootstrap——可拖动模态框(Model)

还是上一个小项目&#xff0c;o(╥﹏╥)o&#xff0c;要实现点击一个div或者button或者一个东西然后可以弹出一个浮在最上面的弹框。网上找了找&#xff0c;发现Bootstrap的Model弹出框可以实现该功能&#xff0c;因此学习了一下&#xff0c;实现了基本弹框功能&#xff08;可拖…

mfcc中的fft操作_简化音频数据:FFT,STFT和MFCC

mfcc中的fft操作What we should know about sound. Sound is produced when there’s an object that vibrates and those vibrations determine the oscillation of air molecules which creates an alternation of air pressure and this high pressure alternated with low …

PHP绘制3D图形

PEAR提供了Image_3D Package来创建3D图像。图像或光线在3D空间中按照X、Y 、Z 坐标定位。生成的图像将呈现在2D空间中&#xff0c;可以存储为 PNG、SVG 格式&#xff0c;或输出到Shell。通过Image_3D可以很方便生成一些简单的3D对象&#xff0c;例如立方体、锥体、球体、文本和…

r语言怎么以第二列绘制线图_用卫星图像绘制世界海岸线图-第二部分

r语言怎么以第二列绘制线图Part I of this blog series is here.本博客系列的第一部分 在这里 。 At the UKHO we are interested in the oceans, the seabed and the coastline — not to mention everything in and on them! In our previous blog, we (the UKHO Data Scien…

JSP基础--动作标签

JSP基础--动作标签 JSP动作标签 1 JSP动作标签概述 动作标签的作用是用来简化Java脚本的&#xff01; JSP动作标签是JavaWeb内置的动作标签&#xff0c;它们是已经定义好的动作标签&#xff0c;我们可以拿来直接使用。 如果JSP动作标签不够用时&#xff0c;还可以使用自定义标…

rcp rapido_Rapido使用数据改善乘车调度

rcp rapidoGiven our last blog post of the series, which can be found here :鉴于我们在该系列中的最后一篇博客文章&#xff0c;可以在这里找到&#xff1a; We thought it would be helpful to explain how we implemented all of the above into an on-ground experimen…

SSRS:之为用户“NT AUTHORITY\NETWORK SERVICE”授予的权限不足,无法执行此操作。 (rsAccessDenied)...

错误信息&#xff1a;为用户“NT AUTHORITY\NETWORK SERVICE”授予的权限不足&#xff0c;无法执行此操作。 (rsAccessDenied)如图&#xff1a;解决方案之检查顺序&#xff1a;1.检查报表的执行服务帐户。使用“ Reporting Services 配置管理器”。2.检查数据库安全 - 登录名 中…

飞机上的氧气面罩有什么用_第2部分—另一个面罩检测器……(

飞机上的氧气面罩有什么用This article is part of a series where I will be documenting my journey on the development of a social distancing feedback system for the blind as part of the OpenCV Spatial Competition. Check out the full series: Part 1, Part 2.本文…

经典网络流题目模板(P3376 + P2756 + P3381 : 最大流 + 二分图匹配 + 最小费用最大流)...

题目来源 P3376 【模板】网络最大流P2756 飞行员配对方案问题P3381 【模板】最小费用最大流最大流 最大流问题是网络流的经典类型之一&#xff0c;用处广泛&#xff0c;个人认为网络流问题最具特点的操作就是建反向边&#xff0c;这样相当于给了反悔的机会&#xff0c;不断地求…

数字经济的核心是对大数据_大数据崛起为数字世界的核心润滑剂

数字经济的核心是对大数据“Information is the oil of the 21st century, and analytics is the combustion engine”.“信息是21世纪的石油&#xff0c;分析是内燃机”。 — Peter Sondergaard, Senior Vice President of Gartner Research.— Gartner研究部高级副总裁Peter…

制作简单的WIFI干扰器

原教程链接:http://www.freebuf.com/geek/133161.htmlgithub 1.准备材料 制作需要的材料有 nodemcu开发版IIC通信 128*64 OLED液晶屏电线按钮开关万能板排针(自选)双面胶(自选)参考2.准备焊接 引脚焊接参考 oled按钮效果3.刷入固件 下载烧录工具:ESP8266Flasher.exe 下载固件:…

Snipaste截图

绘图绘色&#xff0c;描述加图片能更加说明问题的本质。今天推荐一款多功能的截图snipaste... 欣赏绘色 常见报错 解决方案&#xff1a; 下载相关的DLL即可解决&#xff0c; 请根据你操作系统的版本&#xff08;32位/64位&#xff09;&#xff0c;下载并安装相应的微软 Visual …

azure第一个月_MLOps:两个Azure管道的故事

azure第一个月Luuk van der Velden and Rik Jongerius卢克范德费尔登(Luuk van der Velden)和里克 琼格里乌斯( Rik Jongerius) 目标 (Goal) MLOps seeks to deliver fresh and reliable AI products through continuous integration, continuous training and continuous del…

VS2008 开发设计MOSS工作流 URN 注意了

最近学习MOSS 很苦恼&#xff0c;进度也很慢&#xff0c;最近在学习VS2008开发工作流&#xff0c;其中有结合INFOPATH 2007来做, 出现个BUG或者说是设置的问题,整整花了我一天工作时间&#xff0c;是这样的: 在部署的时候关于URN&#xff0c;大部分的教程都是这样的说的&#…

ArangoDB Foxx service 使用

备注&#xff1a;项目使用的是github https://github.com/arangodb-foxx/demo-hello-foxx1. git clonegit clone https://github.com/arangodb-foxx/demo-hello-foxx.git 2. 安装foxx servicefoxx-manager install demo-hello-foxx /demoapp 3. 效果自动生成的swagger 文档项目…

编译原理 数据流方程_数据科学中最可悲的方程式

编译原理 数据流方程重点 (Top highlight)Prepare a box of tissues! I’m about to drop a truth bomb about statistics and data science that’ll bring tears to your eyes.准备一盒纸巾&#xff01; 我将投放一本关于统计和数据科学的真相炸弹&#xff0c;这会让您眼泪汪…

iOS-FMDB

2019独角兽企业重金招聘Python工程师标准>>> #import <Foundation/Foundation.h> #import <FMDatabase.h> #import "MyModel.h"interface FMDBManager : NSObject {FMDatabase *_dataBase; }(instancetype)shareInstance;- (BOOL)insert:(MyM…

解决朋友圈压缩_朋友中最有趣的朋友[已解决]

解决朋友圈压缩We live in uncertain times.我们生活在不确定的时代。 We don’t know when we’re going back to school or the office. We don’t know when we’ll be able to sit inside at a restaurant. We don’t even know when we’ll be able to mosh at a Korn co…