赞
踩
c#允许使用 var 声明变量,编译期会通过初始化语句右侧的表达式推断出变量的类型。
// i is compiled as an int var i = 5; // s is compiled as a string var s = "Hello"; // a is compiled as int[] var a = new[] { 0, 1, 2 }; // expr is compiled as IEnumerable<Customer> // or perhaps IQueryable<Customer> var expr = from c in customers where c.City == "London" select c; // anon is compiled as an anonymous type var anon = new { Name = "Terry", Age = 34 }; // list is compiled as List<int> var list = new List<int>();
使用var来声明变量,我们可以把注意力放在更为重要的使用部分,只需要准确的变量名,既提供正确的语义,剩下的编译期会去操心变量的类型与后面的使用方法是不是匹配。
甚至有的时候,开发者可能会定义错误或者不合适的数据类型,反而不如var方式声明隐式类型。 如 C# :IQueryable & IEnumerable 中:
var q = from c in dbContext.Customers Where c.City == "London" select c;
var finalAnswer = from c in q order by c.Name select c;
上面的 q 编译期会通过表达式,将 q 退段位 IQueryable 类型,从而将 Where 表达式与第二行的 order by 进行表达树组合,在一次sql查询操作里完成。
假如开发者明确声明了 q 的类型,但是声明为 IEnumerable,那么在第一行结束,就会把 Where 查询到的所有数据传到本地,之后再进行排序。
IEnumerable<Customer> q = from c in dbContext.Customers Where c.City == "London" select c;
var finalAnswer = from c in q order by c.Name select c;
在使用隐式类型的时候,需要注意的是类型转换的问题。
有些转换是宽化转化(widening conversion),有些则是窄化转换(narrowing conversion),窄化转换会导致精度下降,例如从 long 到 int 的转换。
public class VarTypeConvert { public static void Run() { IMagicNumberGenerator<double> doubleMNGenerator = new DoubleMNGenerator(); IMagicNumberGenerator<float> floatMNGenerator = new FloatMNGenerator(); IMagicNumberGenerator<int> intMNGenerator = new IntMNGenerator(); var d = doubleMNGenerator.GenerateMagicNumber(); var dtotal = 100 * d / 6; Console.WriteLine($"Double magic number: {d}, Total: {dtotal}"); var f = floatMNGenerator.GenerateMagicNumber(); var ftotal = 100 * f / 6; Console.WriteLine($"Float magic number: {f}, Total: {ftotal}"); var i = intMNGenerator.GenerateMagicNumber(); var itotal = 100 * i / 6; Console.WriteLine($"Int magic number: {i}, Total: {itotal}"); } } -----------------Output-------------------------- Double magic number: 1, Total: 16.666666666666668 Float magic number: 1, Total: 16.666666 Int magic number: 1, Total: 16
都是计算 100 * x / 6,但是由于 x 的类型不同,隐式类型的公式结果大不相同。由于代码采用了隐式类型的局部变量,因此编译期自己推断变量的类型,即根据赋值符号右边的部分做出最佳选择。
即使我们将公式结果定义为 double,由于 MagicNumbe 是int类型,程序仍然会按照整数运算规则进行计算:
var i = intMNGenerator.GenerateMagicNumber();
double dtotal = 100 * i / 6;
Console.WriteLine($"Int magic number: {i}, Total: {dtotal}");
// Output: Int magic number: 1, Total: 16
隐式类型的局部变量是为了支持匿名类型机制而加入 c# 语言的。如下匿名类型:
var v = new { Amount = 108, Message = "Hello" };
Console.WriteLine(v.Amount + v.Message);
我们在使用查询表达式的时候,常会使用匿名类型,从而对每个对象的属性子集处理和返回:
var productQuery =
from prod in products
select new { prod.Color, prod.Price };
foreach (var v in productQuery)
{
Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}
匿名类型为我们提供了一种将只读属性封装到单个对象,又不需要显示先定义类型的便捷方法。类名由编译期生成,每个属性的类型由编译期推断。
匿名类型支持采用 with 表达式形式的非破坏性修改,从而使开发者可以创造匿名类型的新实例。
var p1 = new NamedPoint("A", 0, 0);
Console.WriteLine($"{nameof(p1)}: {p1}"); // output: p1: NamedPoint { Name = A, X = 0, Y = 0 }
var p2 = p1 with { Name = "B", X = 5 };
Console.WriteLine($"{nameof(p2)}: {p2}"); // output: p2: NamedPoint { Name = B, X = 5, Y = 0 }
值得注意的是,匿名类的 Equals 和 GetHashCode 方法是根据属性的 Equals 和 GetHashCode 定义的,因此仅当同一匿名类型两个实例所有属性都相等,这两个实例相等。
编译时类型即直接声明的类型,或推断类型(c# 隐式类型)。
需要注意的是隐式类型的变量,编译器推断出来的类型不一定是我们期望的类型,比如:
// 推断类型为字符串
var message1 = "This is a string of characters";
// 期望类型为 字符的枚举数组
IEnumerable<char> message2 = "This is a string of characters";
运行时类型则是在代码运行阶段实际使用的类型。编译时类型确定编译期执行的所有操作,包括方法调用解析、重载决策以及可用的隐式显示强制转换。运行时类型确定在运行时解析度所有操作,包括调度虚拟方法、计算 is 和 switch 表达式以及其他类型测试API。
object o = Factory.GetObject();
MyType t = o as MyType;
// as 在运行时判断是否是 MyType,如果是则进行类型转换,否则返回 null
if (t != null) ...
else ...
再来看一个编译时类型和运行时类型在类型转换时的例子:
namespace LearnCSharp.Type { public class MyType { public int data { get; set; } } public class SecondType { private MyType _value; public SecondType() { _value = new MyType() { data = 10 }; } // 定义类型转换从 SecondType -> MyType public static implicit operator MyType(SecondType st) { return st._value; } } public class LTypeFactory { public static SecondType GetObject() { return new SecondType(); } } // 测试类型转换 public class Program { public static void Main(string[] args) { SecondType secondType = new SecondType(); // 根据SecondType 中的声明,可以成功转换类型 MyType myType = secondType; Console.WriteLine($"Convert SecondType to MyType success: Data = {myType.data}"); object o = LTypeFactory.GetObject(); MyType t = o as MyType; // 由于运行时类型并不是 MyType,as运算符只会判断运行期类型,不会执行强制类型转换 if (t != null) { Console.WriteLine($"Convert object to MyType success: Data = {t.data}"); } else { Console.WriteLine("Convert object to MyType failed"); } try { MyType t1; t1 = (MyType) o; Console.WriteLine($"Convert object to MyType success: Data = {t1.data}"); } catch (Exception ex) // 强制类型转换以对象的编译期类型为依据,所以会认为 o 是 object,而非 SecondType,从而强制转换失败 { Console.WriteLine($"Convert object to MyType failed: {ex.Message}"); } VarTypeConvert.Run(); } } } --------------------Output---------------------------------- Convert SecondType to MyType success: Data = 10 Convert object to MyType failed Exception thrown: 'System.InvalidCastException' in LearnCSharp.dll Convert object to MyType failed: Unable to cast object of type 'LearnCSharpType.SecondType' to type 'LearnCSharp.Type.MyType'.
装箱是将值类型转换为object类型,或由此值类型实现的任何接口类型的过程,新创建的引用对象相当于一个箱子,分配在堆上面,其中含有原值的一份拷贝。
取消装箱则是从对象中提取值类型。会把已经装箱的那个值拷贝一份出来。
相对于简单的赋值而言,装箱和取消装箱过程需要大量的计算,装箱时,需要分配构造新对象。取消装箱所需要的强制转换也需要大量计算。
int i = 123;
object o = i; // explicit boxing
int i = 123; // a value type
object o = i; // boxing
int j = (int)o; // unboxing
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。