1.前言:
博文首先区分清楚无类型化,弱类型化和强类型化;静态的类型化语言和动态的类型化语言等概念;
接着说说动态编程在C#中发展,这包括var关键字的出现,匿名类型,dynamic类型等;
最后说回到ASP.NET MVC的ViewBag。
这就引入了2个问题:
为什么说C#是静态的类型化的语言,而Javascript是动态的类型化的语言?
为什么说C#是强类型化的语言,而Javascript是弱类型化的语言?
先暂且不谈无类型化的语言,要想解释清楚上面两个问题,首先得清楚这样的四个概念:弱类型、强类型和静态类型、动态类型。
2.1 静态类型和动态类型:
每一种程序设计语言都提供一定的类型检查机制,类型检查可以在编译时进行,也可以在运行时进行,分别称做静态类型检查和动态类型检查。
类型检查,可以说包含了语法检查;就是说在进行类型检查时不仅得满足这门语言的语法规则,还得满足一些的规则。
简单讲,静态类型和动态类型其实就是一种类型检查机制;
2.2 弱类型和强类型:
一门程序设计语言采取什么样的类型检查机制,对语言的实现效率有很大影响;
而在对这门语言书写的程序进行类型检查时,会根据这门语言的语法体系制定一系列的规则;
满足了这些规则,才能说书写的代码语法正确;
我们是根据这些规则来判断这门语言到底是强类型化的还是弱类型化的。
比如强类型化的语言需要满足下面几条规则:
a.每个变量都能在编译时确定唯一的类型
b.当变量允许存储1个以上类型的值时,可以对值进行检查(数组,List)
c.对于某种运算连接在一起的两个变量的所有实例都要进行静态类型检查,看它们的数据类型是否一致.(1+“1”,1-“1”);
等等
只有在进行类型检查时全部满足了上述的规则才是强类型化的语言,反之就是弱类型化的语言。
总结一下:说到底静态类型和动态类型是一种类型检查机制,强类型和弱类型是对一系列类型检查规则的总称。
2.3 回答提出的两个问题:
这样有了基本的概念认知之后,就很好回答刚才引出来的两个问题了。
问题一:为什么说C#是静态的类型化的语言,而Javascript是动态的类型化的语言?
因为C#是在编译时进行类型检查的;要想运行C#书写的程序,得先将C#代码编译成为IL代码,这里就进行了类型检查;即静态类型检查。
而Javascript是一门解释运行的语言,在运行Js书写的程序前,无需进行编译,它是在程序启动运行时才进行类型检查的,类型检查通过了,就会解释执行当前行代码;即动态类型检查。
问题二:为什么说C#是强类型化的语言,而Javascript是弱类型化的语言?
因为C#在进行编译检查时,满足了强类型化语言所定义规则;而Js不满足这些规则,所以说C#是强类型化的语言,Js是弱类型化的语言。
附上语言分类图,以及知乎上的一个参考链接,那里说的比较学术性。
3.动态编程在C#里的发展:
在.NET Framework 的以前版本中,var关键字和匿名方法开辟出C#的“动态编程”路径。在版本4中,新增了dynamic类型,这才使得.NET Framework真正有了动态编程的能力。
3.1 var关键字和匿名方法:
Var关键字,用于隐式声明变量的类型,在编译时,编译器会进行类型推断,会确认变量的类型是什么。(智能感知在代码书写完成后,就能知道变量的类型)所以隐式类型的变量是强类型的。
Var在很多场合不是必须,但也有一些场合是必须,比如Linq查询中。
如下图所示:
两个查询表达式。
在第一个表达式中,var 的使用是允许的,但不是必需的,因为查询结果的类型可以明确表述为 IEnumerable
。
但是,在第二个表达式中,必须使用 var,因为结果是匿名类型的集合,并且该类型的名称只有编译器本身才可访问。
需要注意的是,在示例 #2 中,foreach 迭代变量 item 必须也为隐式类型。即用var关键字声明变量,因为我们不知道该类型的名称,只有编译器知道。
接着是匿名方法:
在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法。
C# 2.0 引入了匿名方法,
而在 C# 3.0 及更高版本中,Lambda 表达式取代了匿名方法,作为编写内联代码的首选方式。
3.2 dynamic关键字:
其实这前面说的仍是静态类型检查机制。对C#语言本身影响不大,直到dynamic类型的出现,才使得C#可以在运行时再确定变量类型,也就是部分代码进行动态类型检查,这才将弱类型化语言的一些特性赋予了C#。
dynamic 类型允许编写忽略编译期间的类型检查代码 。编译器假定,给dynamic类型的对象定义的任何操作都是有效的。如果该操作无效,则在代码运行之前不会检测该错误。
与var关键字不同,定义为dynamic的对象可以在运行期间改变其类型。
//下面的这些代码都是可以正常运行的
dynamic dyn;
dyn = 100;
Console.WriteLine(dyn.GetType());
Console.WriteLine(dyn);
dyn = "This is a string";
Console.WriteLine(dyn.GetType());
Console.WriteLine(dyn);
dyn = new SomeClass();
在使用var关键字时,对象类型的确定会延迟。类型一旦确定,就不能改变。动态对象(dynamic)的类型可以改变,而且可以改变多次,这不同于把对象的类型强制转换为另一种类型。在运行前,它始终只有一个类型,那就是dynamic。在运行时,它的类型可以进行改变。
这些动态功能是Dynamic Language Runtime(动态语言运行时)的一部分。
接着我们看看几个实例:
3.2.1 使用ExpandoObject类型来创建自己的动态类型:
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ExpandoObjectSample
{
class Program
{
static void Main(string[] args)
{
dynamic expObj = new ExpandoObject();
//字段、属性
expObj.FirstName = "Liu1";
expObj.LastName = "Liu2";
Console.WriteLine(expObj.FirstName + " " + expObj.LastName);
//方法
Funcstring> GetTomorrow = today => today.AddDays(1).ToShortDateString();
expObj.GetTomorrowDate = GetTomorrow;
Console.WriteLine("Tomorrow is {0}", expObj.GetTomorrowDate(DateTime.Now));
//集合
expObj.Friends = new List();
expObj.Friends.Add(new Person() { FirstName = "Bob", LastName = "Jones" });
expObj.Friends.Add(new Person() { FirstName = "Liu", LastName = "Jones" });
expObj.Friends.Add(new Person() { FirstName = "Tao", LastName = "Jones" });
foreach (Person friend in expObj.Friends)
{
Console.WriteLine(friend.FirstName + " " + friend.LastName);
}
Console.ReadLine();
}
}
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string GetFullName()
{
return string.Concat(FirstName, " ", LastName);
}
}
}
这上面在使用FirstName ,LastName,GetTomorrowDate ,Friends 时,就像它本来就已经定义在ExpandoObject类型中一样,但ExpandoObject是.NET Framework定义的一个类型。它没有包含这些字段,属性,方法的定义。点我查看ExpandoObject源码
这里面ExpandoObject类封装起来的其实是这样的三个方法,代码如下,可以拷贝到控制台中单步运行一下;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TaoDynamicObject
{
class Program
{
static void Main(string[] args)
{
dynamic expObj = new TaoDynamicObject();
//字段、属性
expObj.FirstName = "Liu1";
expObj.LastName = "Liu2";
Console.WriteLine(expObj.FirstName + " " + expObj.LastName);
//方法
Funcstring> GetTomorrow = today => today.AddDays(1).ToShortDateString();
expObj.GetTomorrowDate = GetTomorrow;
Console.WriteLine("Tomorrow is {0}", expObj.GetTomorrowDate(DateTime.Now));
//集合
expObj.Friends = new List();
expObj.Friends.Add(new Person() { FirstName = "Bob", LastName = "Jones" });
expObj.Friends.Add(new Person() { FirstName = "Liu", LastName = "Jones" });
expObj.Friends.Add(new Person() { FirstName = "Tao", LastName = "Jones" });
foreach (Person friend in expObj.Friends)
{
Console.WriteLine(friend.FirstName + " " + friend.LastName);
}
Console.ReadLine();
}
}
class TaoDynamicObject : DynamicObject
{
Dictionary<string, Object> _dynamicData = new Dictionary<string, object>() { };
public override bool TryGetMember(GetMemberBinder binder,out object result)
{
bool success = false;
result = null;
if (_dynamicData.ContainsKey(binder.Name))
{
result = _dynamicData[binder.Name];
success = true;
}
else
{
result = "Property Not Found!";
success = false;
}
return success;
}
//如何进行类型转换的?从object转换成为相应的类型
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_dynamicData[binder.Name] = value;
return true;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
dynamic method = _dynamicData[binder.Name];
//这里如何处理多种类型的参数?
//1.存储在托管堆上的对象,有自描述其类型的信息
//2.会根据调用方法规定的参数类型进行强制转换
result = method((DateTime)args[0]);
return result != null;
}
}
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string GetFullName()
{
return string.Concat(FirstName, " ", LastName);
}
}
}
在main函数中虽然没有明确的调用TryGetMember,TrySetMember,TryInvokeMember这三个函数,但在编译成IL时,编译器帮我们完成了这一步。
3.2.2 这个例子是为了说明少用dynamic类型:
当我们在使用dynamic类型时,虽然语法看起来很简洁,就像是在使用如int,string这样的关键字,并且还能够获得弱类型化的语言的一些功能,但在这儿背后是CLR做了大量的工作。
下面我们用一个实例来说明这点:
整体代码如下:
using System;
namespace DeCompile
{
class Program
{
static void Main(string[] args)
{
StaticClass staticObject = new StaticClass();
//DynamicClass dynamicObject = new DynamicClass();
Console.WriteLine(staticObject.IntValue);
//Console.WriteLine(dynamicObject.DynValue);
Console.ReadLine();
}
}
class StaticClass
{
public int IntValue = 100;
}
class DynamicClass
{
public dynamic DynValue = 100;
}
}
我先注释了DynamicClass 这个类型的对象。在生成.exe程序之后用IL dasm.exe反编译成IL代码。
开始菜单中,找到并打开下图所示的 VS2015 开发人员命令提示
输入ildasm,并回车。
这里生成的IL代码很少。
接着注释掉StaticClass ,并把DynamicClass 的注释去掉,在进行反编译。
就会得到如下所示的IL代码:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 119 (0x77)
.maxstack 9
.locals init ([0] class DeCompile.DynamicClass dynamicObject)
IL_0000: newobj instance void DeCompile.DynamicClass::.ctor()
IL_0005: stloc.0
IL_0006: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> DeCompile.Program/'<>o__0'::'<>p__0'
IL_000b: brtrue.s IL_004c
IL_000d: ldc.i4 0x100
IL_0012: ldstr "WriteLine"
IL_0017: ldnull
IL_0018: ldtoken DeCompile.Program
IL_001d: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0022: ldc.i4.2
IL_0023: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
IL_0028: dup
IL_0029: ldc.i4.0
IL_002a: ldc.i4.s 33
IL_002c: ldnull
IL_002d: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
string)
IL_0032: stelem.ref
IL_0033: dup
IL_0034: ldc.i4.1
IL_0035: ldc.i4.0
IL_0036: ldnull
IL_0037: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
string)
IL_003c: stelem.ref
IL_003d: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,
string,
class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>,
class [mscorlib]System.Type,
class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
IL_0042: call class [System.Core]System.Runtime.CompilerServices.CallSite`10> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
IL_0047: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> DeCompile.Program/'<>o__0'::'<>p__0'
IL_004c: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> DeCompile.Program/'<>o__0'::'<>p__0'
IL_0051: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>>::Target
IL_0056: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> DeCompile.Program/'<>o__0'::'<>p__0'
IL_005b: ldtoken [mscorlib]System.Console
IL_0060: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0065: ldloc.0
IL_0066: ldfld object DeCompile.DynamicClass::DynValue
IL_006b: callvirt instance void class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>::Invoke(!0,
!1,
!2)
IL_0070: call string [mscorlib]System.Console::ReadLine()
IL_0075: pop
IL_0076: ret
} // end of method Program::Main
看到上面生成的这么多看不太懂的代码,你就知道了,为什么不建议使用dynamic了吧。
4.ASP.NET MVC里的ViewBag:
说到ViewBag,就会说到ViewData和ViewDataDictionary
结合上面说到得dynamic类型,我们知道在页面中应该尽可能少的使用ViewBag,而更多的要使用强类型视图 即@model 语法。
5.结语:
博文中的概念性叙述比较多,有什么说的不对的地方,欢迎各位留言评论交流。