C#编译器优化那点事 c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错 webAPI 控制器(Controller)太多怎么办? .NET MVC项目设置包含Areas中的页面为默认启动页 (五)Net Core使用静态文件 学习ASP.NET Core Razor 编程系列八——并发处理

时间:2023-03-08 17:39:12
C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

C#编译器优化那点事

使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的。
优化代码开关即optimize开关,和debug开关一起,有以下几种组合。
C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

在Visual Sutdio中新建一个C#项目时,
项目的“调试”(Debug)配置的是/optimize-和/debug:full开关,
而“发布”(Release)配置指定的是/optimize+和/debug:pdbonly开关

optimize-/+决定了编译器是否优化代码,optimize-就是不优化了,但是通常,有一些基本的“优化”工作,无论是否指定optimize+,都会执行。

optimize- and optimize+

该项功能主要用于动态语义分析,帮助我们更好地编写代码。

  • 常量计算

    在写程序的时候,有时能看见代码下面划了一道红波浪线,那就是编译器动态检查。常量计算,就是这样,编译器会计算常量,帮助判断其他错误。
    C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

  • 简单分支检查

    如果swtich写了两个以上的相同条件,或者分支明显无法访问到,都会弹出提示。
    C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

  • 未使用变量

    不多说明,直接看图。
    C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

  • 使用未赋值变量

    不多说,看图。
    C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

局限

使用变量参与计算,随便写一个算式,就可以绕过一些检查,虽然我们看来是明显有问题的。
C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

optimize+ only

首先需要了解c#代码编译的过程,如下图:
C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理
图片来自http://www.cnblogs.com/rush/p/3155665.html

C# compiler将C#代码生成IL代码的就是所谓的编译器优化。先说重点。
.NET的JIT机制,主要优化在JIT中完成,编译器optimize只做一点简单的工作。(划重点)

探究一下到底干了点啥吧,以下是使用到的工具。

Tools:
Visual studio 2017 community targeting .net core 2.0
IL DASM(vs自带)

使用IL DASM可以查看编译器生成的IL代码,这样就能看到优化的作用了。IL代码的用途与机制不是本文的重点,不明白的同学可以先去看看《C# via CLR》(好书推荐)。

按照优化的类型进行了简单的分类。

  • 从未使用变量

    代码如下:

using System;
using System.Threading.Tasks;

namespace CompileOpt
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 3;
            Console.WriteLine("sg");
        }
    }
}

未优化的时候

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       15 (0xf)
  .maxstack  1
  .locals init (int32 V_0)
  IL_0000:  nop
  IL_0001:  ldc.i4.3
  IL_0002:  stloc.0
  IL_0003:  ldstr      "sg"
  IL_0008:  call       void [System.Console]System.Console::WriteLine(string)
  IL_000d:  nop
  IL_000e:  ret
} // end of method Program::Main

使用优化开关优化之后:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       11 (0xb)
  .maxstack  8
  IL_0000:  ldstr      "sg"
  IL_0005:  call       void [System.Console]System.Console::WriteLine(string)
  IL_000a:  ret
} // end of method Program::Main

.locals init (int32 V_0)消失了(局部变量,类型为int32)
ldc.i4.3(将3推送到堆栈上)和stloc.0(将值从堆栈弹出到局部变量 0)也消失了。
所以,整个没有使用的变量,在设置为优化的时候,就直接消失了,就像从来没有写过一样。

  • 空try catch语句

    代码如下:

using System;
using System.Threading.Tasks;

namespace CompileOpt
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {

            }
            catch (Exception)
            {
                Console.WriteLine(DateTime.Now);
            }

            try
            {

            }
            catch (Exception)
            {
                Console.WriteLine(DateTime.Now);

            }
            finally
            {
                Console.WriteLine(DateTime.Now);

            }
        }
    }
}

未优化

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       74 (0x4a)
  .maxstack  1
  IL_0000:  nop
  .try
  {
    IL_0001:  nop
    IL_0002:  nop
    IL_0003:  leave.s    IL_001a
  }  // end .try
  catch [System.Runtime]System.Exception
  {
    IL_0005:  pop
    IL_0006:  nop
    IL_0007:  call       valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()
    IL_000c:  box        [System.Runtime]System.DateTime
    IL_0011:  call       void [System.Console]System.Console::WriteLine(object)
    IL_0016:  nop
    IL_0017:  nop
    IL_0018:  leave.s    IL_001a
  }  // end handler
  IL_001a:  nop
  .try
  {
    .try
    {
      IL_001b:  nop
      IL_001c:  nop
      IL_001d:  leave.s    IL_0034
    }  // end .try
    catch [System.Runtime]System.Exception
    {
      IL_001f:  pop
      IL_0020:  nop
      IL_0021:  call       valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()
      IL_0026:  box        [System.Runtime]System.DateTime
      IL_002b:  call       void [System.Console]System.Console::WriteLine(object)
      IL_0030:  nop
      IL_0031:  nop
      IL_0032:  leave.s    IL_0034
    }  // end handler
    IL_0034:  leave.s    IL_0049
  }  // end .try
  finally
  {
    IL_0036:  nop
    IL_0037:  call       valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()
    IL_003c:  box        [System.Runtime]System.DateTime
    IL_0041:  call       void [System.Console]System.Console::WriteLine(object)
    IL_0046:  nop
    IL_0047:  nop
    IL_0048:  endfinally
  }  // end handler
  IL_0049:  ret
} // end of method Program::Main

优化开关开启:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       19 (0x13)
  .maxstack  1
  .try
  {
    IL_0000:  leave.s    IL_0012
  }  // end .try
  finally
  {
    IL_0002:  call       valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()
    IL_0007:  box        [System.Runtime]System.DateTime
    IL_000c:  call       void [System.Console]System.Console::WriteLine(object)
    IL_0011:  endfinally
  }  // end handler
  IL_0012:  ret
} // end of method Program::Main

很明显可以看到,空的try catch直接消失了,但是空的try catch finally代码是不会消失的,但是也不会直接调用finally内的代码(即还是会生成try代码段)。

  • 分支简化

    代码如下:

using System;
using System.Threading.Tasks;

namespace CompileOpt
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 3;
            if (x == 3)
                goto LABEL1;
            else
                goto LABEL2;
            LABEL2: return;
            LABEL1: return;
        }
    }
}

未优化的情况下:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       22 (0x16)
  .maxstack  2
  .locals init (int32 V_0,
           bool V_1)
  IL_0000:  nop
  IL_0001:  ldc.i4.3
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  ldc.i4.3
  IL_0005:  ceq
  IL_0007:  stloc.1
  IL_0008:  ldloc.1
  IL_0009:  brfalse.s  IL_000d
  IL_000b:  br.s       IL_0012
  IL_000d:  br.s       IL_000f
  IL_000f:  nop
  IL_0010:  br.s       IL_0015
  IL_0012:  nop
  IL_0013:  br.s       IL_0015
  IL_0015:  ret
} // end of method Program::Main

优化:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       5 (0x5)
  .maxstack  8
  IL_0000:  ldc.i4.3
  IL_0001:  ldc.i4.3
  IL_0002:  pop
  IL_0003:  pop
  IL_0004:  ret
} // end of method Program::Main

优化的情况下,一些分支会被简化,使得调用更加简洁。

  • 跳转简化

    代码如下:

using System;
using System.Threading.Tasks;

namespace CompileOpt
{
    class Program
    {
        static void Main(string[] args)
        {
            goto LABEL1;
            LABEL2: Console.WriteLine("234");
            Console.WriteLine("123");
            return;
            LABEL1: goto LABEL2;
        }
    }
}

未优化:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       32 (0x20)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  br.s       IL_001c
  IL_0003:  nop
  IL_0004:  ldstr      "234"
  IL_0009:  call       void [System.Console]System.Console::WriteLine(string)
  IL_000e:  nop
  IL_000f:  ldstr      "123"
  IL_0014:  call       void [System.Console]System.Console::WriteLine(string)
  IL_0019:  nop
  IL_001a:  br.s       IL_001f
  IL_001c:  nop
  IL_001d:  br.s       IL_0003
  IL_001f:  ret
} // end of method Program::Main

优化后:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       21 (0x15)
  .maxstack  8
  IL_0000:  ldstr      "234"
  IL_0005:  call       void [System.Console]System.Console::WriteLine(string)
  IL_000a:  ldstr      "123"
  IL_000f:  call       void [System.Console]System.Console::WriteLine(string)
  IL_0014:  ret
} // end of method Program::Main

一些多层的标签跳转会得到简化,优化器就是人狠话不多。

  • 临时变量消除

    一些临时变量(中间变量)会被简化消除。代码如下:

using System;
using System.Threading.Tasks;

namespace CompileOpt
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 3; i++)
            {
                Console.WriteLine(i);
            }
            for (int i = 0; i < 3; i++)
            {
                Console.WriteLine(i + 1);
            }
        }
    }
}

只显示最关键的变量声明部分,未优化的代码如下:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       54 (0x36)
  .maxstack  2
  .locals init (int32 V_0,
           bool V_1,
           int32 V_2,
           bool V_3)
  IL_0000:  nop

优化后:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       39 (0x27)
  .maxstack  2
  .locals init (int32 V_0,
           int32 V_1)
  IL_0000:  ldc.i4.0

很显然,中间的bool型比较变量消失了。

  • 空指令删除

    看第一个例子,很明显,代码中没有了nop字段,程序更加紧凑了。

编译器版本不同,对应的优化手段也不尽相同,以上只列出了一些,应该还有一些没有讲到的,欢迎补充。

延伸阅读:.NET中的优化(转载自http://blog.jobbole.com/84712/

在.NET的编译模型中没有链接器。但是有一个源代码编译器(C# compiler)和即时编译器(JIT compiler),源代码编译器只进行很小的一部分优化。比如它不会执行函数内联和循环优化。

从优化能力上来讲RyuJIT和Visual C++有什么不同呢?因为RyuJIT是在运行时完成其工作的,所以它可以完成一些Visual C++不能完成的工作。比如在运行时,RyuJIT可能会判定,在这次程序的运行中一个if语句的条件永远不会为true,所以就可以将它移除。RyuJIT也可以利用他所运行的处理器的能力。比如如果处理器支持SSE4.1,即时编译器就会只写出sumOfCubes函数的SSE4.1指令,让生成打的代码更加紧凑。但是它不能花更多的时间来优化代码,因为即时编译所花的时间会影响到程序的性能。

在当前控制托管代码的能力是很有限的。C#和VB编译器只允许使用/optimize编译器开关打开或者关闭优化功能。为了控制即时编译优化,你可以在方法上使用System.Runtime.Compiler­Services.MethodImpl属性和MethodImplOptions中指定的选项。NoOptimization选项可以关闭优化,NoInlining阻止方法被内联,AggressiveInlining (.NET 4.5)选项推荐(不仅仅是提示)即时编译器将一个方法内联。

结语

话说整点这个东西有点什么用呢?
要说是有助于更好理解.NET的运行机制会不会有人打我...
说点实际的,有的童鞋在写延时程序时,timer.Interval = 10 * 60 * 1000,作为强迫症患者,生怕这么写不好,影响程序执行。但是,这种写法完全不会对程序的执行有任何影响,我认为还应该推荐,因为增加了程序的可读性,上面的代码段就是简单的10分钟,一看就明白,要是算出来反而可读性差。另外,分支简化也有助于我们专心依照业务逻辑去编写代码,而不需要过多考虑代码的分支问题。其他的用途各位看官自行发挥啦。

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理
除非特殊说明,本作品由podolski创作,采用知识共享署名 4.0 国际许可协议进行许可。欢迎转载,转载请保留原文链接~喜欢的观众老爷们可以点下关注或者推荐~

c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错

如果一个对象的值为null,那么它调用扩展方法时会报错吗?

Person p = null ;
p.ExtendMethod();

上述代码出现的情况不会报错,刚开始遇到这种情况时很纳闷,就去问了大牛。大牛解释如下:

扩展函数其实只是为了让代码更具有可读性, 但最终在clr中会翻译成标准的静态函数调用,

比如:  

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理
public static void ExtMethod(this string str)
{
    if(!string.IsNullOrEmpty(str))
    {
         //对str处理
    }
}
C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

调用  "string".ExtMethod()最终会翻译成ExtMethod("string"); 所以即使为null自然也不会报错

null 为什么点不出那个扩展函数?


  通过null获取它自己的方法如下:

  C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

  这样试试 ((string)null).ExtMethod() 肯定能点出来

  C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

  关键是使用扩展函数要看对应的数据类型。

你的大神在骗你
public static void ExtMethod(this string str) 
{
str.ToString();
}
你要是str=null,看下会不会报错呢?不报错只是扩展方法处理了为null的情况,和翻译方式并没有关系

webAPI 控制器(Controller)太多怎么办?

写过接口的同学都知道,接口会越来越多,那么控制器也会越来越多。这时候就需要根据某种业务或特性对controller进行分类然后建立文件夹。

我想到一个折中的方案:伪Areas!

在Areas文件夹下建立对应的文件夹,比如说用户相关的,建立一个Account文件夹

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

图中就是我创建的文件夹及对应的Controller,对应的方法:

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

看到我指定的路由值了

[Route("api/Account/Account/DemoMethod")]

[Route("api/XXX(Areas下对应分类的文件夹名称)/Account/DemoMethod")]

就是在api和controller之间加一层,就是areas下的文件夹的名字,这样就实现了所谓的“伪Areas”,这样也能解决另一个问题。

假如你其他的业务中也需要一个accountcontroller,那么这时候我的这种解决方案就可以满足你的需求。

其实这种方法也可以在项目根目录下的controller文件夹中实现,但是这样比较高大上嘛!

再有就是Areas文件夹不只有controller文件夹,还有model和view,可以创建对应的视图模型。

.NET MVC项目设置包含Areas中的页面为默认启动页

利用vs创建一个MVC项目后,一般的默认启动页是根目录下-->Controllers-->HomeController-->Index这个方法对应的页面。

我先说下创建Areas的流程:

但是我们的controller一般都会建立很多,这样我们就会想建个文件夹按照业务或者其他的分类方式把这么多文件放在不通的文件夹分开,所以就有了区域【Areas】的概念。

首先在根目录下创建一个Areas的文件夹,然后在Areas文件下创建一个区域,也就是我们要分类的文件夹

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

然后VS就会自动创建对应的文件夹和文件配置,如图:

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

然后在controllers文件夹下面创建自己的controller即可,在views文件夹下面创建对应的view页面。

说到这里,我们都创建好了,怎么设置默认启动页,指向我们的controller里面的方法呢?

方法如下,设置controller和action,然后再加上areas即可:

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

 routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Account", action = "Index", id = UrlParameter.Optional }
            ).DataTokens.Add("Area", "Account");

(五)Net Core使用静态文件

一、简介


1、Net Core默认无法访问静态文件,需要在Startup通过代码添加定义。

2、本文介绍两种静态文件目录实现方式。

二、启用默认目录


1、添加图片文件

2、测试访问结果(不能访问)

3、添加定义代码。

4、刷新图片查看结果(正常访问)。

这里有个知识点,我一般使用浏览模式访问,而非F5运行,好处是可以修改代码,Net Core在修改代码之后,刷新访问会自动重新编译。

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

三、启用自定义目录


1、添加自定义目录代码(代码自行解读理解)

2、刷新上述图片访问。(运用上述知识点,理解这个操作)

3、查看运行目录(已经自动生成文件夹)

4、往目录添加一个文件。

5、查看添加文件。(正常访问)

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

BitAdminCore框架作者。 框架演示:http://bit.bitdao.cn 框架使用:https://github.com/chenyinxin/cookiecutter-bitadmin-core 框架交流:QQ群202426919

学习ASP.NET Core Razor 编程系列八——并发处理

学习ASP.NET Core Razor 编程系列目录

学习ASP.NET Core Razor 编程系列一

学习ASP.NET Core Razor 编程系列二——添加一个实体

学习ASP.NET Core Razor 编程系列三——创建数据表及创建项目基本页面

学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面

学习ASP.NET Core Razor 编程系列五——Asp.Net Core Razor新建模板页面

学习ASP.NET Core Razor 编程系列六——数据库初始化

学习ASP.NET Core Razor 编程系列七——修改列表页面

并发异常处理

在Visual Studio 2017的解决方案资源管理器中找到 Pages/Books/Edit.cshtml.cs 文件,鼠标双击打开 ,在代码中找到OnPostAsync方法。并按如下代码进行修改:

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理
public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _context.Attach(Book).State = EntityState.Modified;
            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!_context.Book.Any(e => e.ID == Book.ID))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }

            }
            return RedirectToPage("./Index");
        }
C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

上面的代码功能是当检测到第一个客户端在删除书籍信息时,第二个客户端对要删除的书籍信息进行修改并保存时发生异常。

我们可以进行以下操作来重现上面的异常。

  1. 在 catch (DbUpdateConcurrencyException) 上设置断点。如下图。

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

2. 在Visual Studio 2017中按F5,运行应用程序,在打开的浏览器的一个窗口中,选择一本书籍进行修改。如下图。

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理       3. 在另一个浏览器窗口中,选择同一本书籍信息的“Delete”链接,然后删除此书籍。

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理             4. 在编辑书籍信息的浏览器窗口中,将书籍信息的修改内容保存到数据库。如下图。

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

5. 当两个或更多客户端同时更新记录时,代码通常将检测到并发冲突。如下图。

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

GET请求与POST请求

接下来我们根据 Pages/Books/Edit.cshtml.cs 文件内容来介绍一下请求过程,代码如下:

C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using RazorMvcBooks.Models;

namespace RazorMvcBooks.Pages.Books
{
    public class EditModel : PageModel
    {
        private readonly RazorMvcBooks.Models.BookContext _context;

        public EditModel(RazorMvcBooks.Models.BookContext context)
        {
            _context = context;
        } 

        [BindProperty]
        public Book Book { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }
            Book = await _context.Book.SingleOrDefaultAsync(m => m.ID == id);

            if (Book == null)
            {
                return NotFound();
            }
            return Page();
        }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }
            _context.Attach(Book).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!_context.Book.Any(e => e.ID == Book.ID))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
            return RedirectToPage("./Index");
        }
    }
}
C#编译器优化那点事    c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错    webAPI 控制器(Controller)太多怎么办?    .NET MVC项目设置包含Areas中的页面为默认启动页  (五)Net Core使用静态文件    学习ASP.NET Core Razor 编程系列八——并发处理

1.  当浏览器对Books/Edit 页面发出 HTTP GET 请求时(例如 http://localhost:5000/Books/Edit/9):

  • OnGetAsync 方法从数据库提取书籍信息并把数据传递给Page 方法。
  • Page 方法呈现“Pages/Books/Edit.cshtml”Razor 页面。 Pages/Books/Edit.cshtml 文件包含实体指令 (@model RazorMvcBooks.Pages.Books.EditModel),这使书籍实体在页面上可用。
  • 页面中的表单会显示书籍实体中的值。

2. 当浏览器对Books/Edit 页面发出Post请求时:

  • 此页面上的表单值将绑定到 Book 属性上。 [BindProperty] 特性会启用实体属性绑定。具体代码参见上面的代码。
  • 如果实体对象的属性值中存在错误(例如,ReleaseDate 无法被转换为日期),则会使用已提交的值再次请求表单。
  • 如果实体对象的属性值中没有错误,则把书籍信息保存到数据库。

“Index.cshtml”、“Create.cshtml”和“delete.cshtml”Razor 页面中的 HTTP GET 方法的实现原理与上面所述的Get请求类似。 “Create.cshtml”Razor 页面中的 POST请求方法的实现原理与上面所述的POST请求类似。