【C#复习总结】匿名类型由来

2021-04-13 20:27

阅读:679

标签:强制   stat   private   模型   不可变   class   exception   应该   static   

1 属性

这得先从属性开始说,为什么外部代码访问对象内部的数据用属性而不是直接访问呢,这样岂不是更方便一些,但是事实证明直接访问是不安全的。那么,Anders Hejlsberg(安德斯·海尔斯伯格)就为C#加入了属性这种语法糖,用起来跟数据成员一样,但实际上是 setXX()getXX(),既安全又方便。

属性:是访问对象的首选方式,因为它们禁止外部代码访问对象内部的数据存储机制的实现。

public int MyIntProp
{
    get
    {
    //property get code
    }
    set
    {
    //Proerty set code
    }
}    

1.1 get关键字

get块必须有一个属性的返回值,简单的属性一般与私有字段相关联,以控制对这个字段的访问,此时get块可以直接返回该字段的值,例如:

private int myInt;

public int myIntProp
{
  get
  {
    return myInt;
  }
  set
  {
    //Property set code.
  }
}

类外部的代码不能直接访问这个myInt字段,私有的,必须使用属性来访问该字段。

1.2 set关键字

set函数以类似的方法把一个值赋给字段。这里使用value表示用户提供的属性值:

private int myInt;
public int myIntProp
{
  get 
  {
    return myInt;
  }
  set
  {
    myInt = value;
  }
}

value等于类似与属性相同的值,所以如果属性和字段使用相同的类型,就不必担心数据类型转换了。

这个简单的属性只能直接访问myInt字段。在对操作进行更多的控制的时候,属性的真正作用才能发挥出来,例如,使用下面的代码实现set块: 

set
{
  if(value >= 0 && value 10)
  myInt = value;
}

只用赋给属性的值在1~10之间,才会改myInt。此时,要做一个重要的设计选择:如果使用了无效值,该怎么办:

  • 什么也不做
  • 给字段赋默认值
  • 继续执行,就好像没有发生错误一样,但记录下来该事件,以备将来分析
  • 抛出异常

一般情况下,后面两个选择效果较好,选择哪个选项取决于如何使用类,以及给用户授予多少控制权。抛出异常给用户提供的控制权相当的大,例如:

set
{
  if(value >= 0 && value 10)
    myInt = value;
  else
    throw (new ArgumentOutOfRangeException("myIntProp",value,"myIntProp must be assigned a value between 0 and 10."))
}

这可以在使用属性的代码中通过try...catch...finaly逻辑来处理。

注:属性可以使用virtualoverrideabstract关键字,就像方法一样,但这几个关键字不能用于字段。最后,如上述,访问器可以有自己的访问性。

实例: 

public class MyClass
{
public readonly string Name;
private int intVal;

public int Val
{
  get
  {
  return intVal;
  }
  set
  {
    if (value >= 0 && value 10 )
      intVal = value;
    else
      throw (new ArgumentOutOfRangeException("Val",value,"Val must be assigned a value between 0 ang 10."));
  }
}
public override string ToString()
{
  return "Name:"+Name+"\nVal:"+Val;
}
private MyClass(): this("Default Name")
{

}
public MyClass(string newName)
{
  Name = newName;
  intVal = 0;
}
}

static void Main(string[] args)
{
  Console.WriteLine("Creating object myobj...");
  MyClass myObj = new MyClass("My Object");
  Console.WriteLine("myObj created.");
  for (int i = -1; i 0; i++ )
  {
    try
    {
      Console.WriteLine("\nAttempting to assign {0} to myObj.val...",i);
      myObj.Val = i;
      Console.WriteLine("Value {0} assigned to myObj.val.", myObj.Val);
    }
    catch(Exception e)
    {
      Console.WriteLine("Exception {0} throw.",e.GetType().FullName);
      Console.WriteLine("Message:\n\"{0}\"",e.Message);
    }
  }
  Console.WriteLine("\nOutputting myObj.ToString()...");
  Console.WriteLine(myObj.ToString());
  Console.WriteLine("myObj.ToString() Output.");
  Console.ReadKey();
}

Main()中的的代码创建并使用在MyClass.cs中定义的MyClass类的实例。实例化这个类必须使用非默认的构造函数来进行,因为MyClass类的默认构造函数是私有的。

Main()试着给myObjMyClass的实例)的Val属性赋值。for循环在两次中赋值-10try..catch...结构用于检测抛出的异常。把-1赋给属性时,会抛出System.ArgumentOutOfException类型的异常,catch块中的代码会把改异常的信息输出到控制台窗口中。在下一个循环中,值0成功的赋给了Val属性,通过这个属性再把值赋给私有字段intVal

2 自动属性

但是呢,安德斯还是觉得代码太多,还应该在优化一下,就想出了自动属性。

自动属性。利用自动属性,可以用简化的语法声明属性,C#编译器会自动添加未键入的内容,具体而言,编译器会声明一个用于存储属性的私有字段,并在属性的getset块中使用该字段(非常贴心),我们无需考虑细节。

public int MyIntProp

{
  get;
  set;
}

我们按照通常的方式定义属性的可访问性、类型和名称。但是没有给getset块提供实现的代码。这些块的实现代码(和底层的字段)由编译器提供。

使用自动属性时,只能通过属性访问数据,不能通过底层的私有字段来访问,我们不知道底层私有字段的名称(该名称是编译期间定义的)。但这并不是一个真正意义上的限制,因为可以直接使用属性名。自动属性的唯一限制是他们必须包含getset存储器,无法使用这种方法定义只读和只写属性。

3 对象初始化器

对象初始化器定义:初始化器分为对象初始化器和集合初始化器,此处指我们讲的是对象初始化器,

作用:用较少的代码创建一个新对象并为对象的若干属性和公共数据成员进行赋值。

谈到初始化器,先谈一下构造函数,构造从表面意思就知道这是用来构建类的(当然初始化一些成员也是属于构建的范围,但还有其他作用)

对象初始化过程:先定义类的属性,再实例化和初始化这个类。

定义类的属性用自动属性来定义,实例化和初始化这个类的一个对象实例就必须用C#里面的默认的无参构造函数来实现下段代码。

先看一个类定义:

public class Curry
{
    public string MainIngredient { get; set; }
    public string Style { get; set; }
    public int Spiciness { get; set; }
}

这个类有3 个属性,用自动属性语法来定义。如果希望实例化和初始化这个类的一个对象实例,就必须执行如下几个语句:

  1. 第一种方式
Curry tastyCurry = new Curry();

tastyCurry.MainIngredient = "panir tikka";

tastyCurry.Style = "jalfrezi";

tastyCurry.Spiciness = 8;

如果类定义中未包含构造函数,这段代码就使用C#编译器提供的默认无参数构造函数。这种方式还是有点复杂,应该还有比这个简单的,你猜对了。

2.第二种方式(去掉括号)

Curry tastyCurry=new Curry
{
    tastyCurry.MainIngredient = "panir tikka",

    tastyCurry.Style = "jalfrezi",

    tastyCurry.Spiciness = 8,
}

然后问题就来了,如果你有10个数据成员,实例化和初始化这个类的一个对象实例要写多少个?为了简化这个过程,安德斯又机灵了一下想到了更高级的方式,采用一个合适的非默认构造函数。

如下:

Class tastyCurry =new Curry(“panir tikka ”, “jalfrezi”,8);

这段代码工作的很好,它会强制使用Curry类的代码使用这个构造函数,这将阻止前面默认使用无参构造函数的代码运行。

4 匿名类型

你以为这样就非常方便了么,只能说你太年轻,天外有天,人外有人,看看我们的题目,对了,就是它,我们的主角,匿名类型。

匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。 类型名由编译器生成,并且不能在源代码级使用。 每个属性的类型由编译器推断。可通过使用 new 运算符和对象初始值创建匿名类型。

来,我们举个栗子。

以下示例显示了用两个名为 Amount Message 的属性进行初始化的匿名类型。

var v = new { MainIngredient =“panir tikka ”, Style =“jalfrezi” Spiciness=8};  

Console.WriteLine(v.MainIngredient + v.Style+v.Spiciness);  

看见了么,对,没错,就是这么简单。

 

备注:关于C# 的匿名类型为什么要限制属性为只读呢?

来自知乎网友的一段话我觉得说的挺好的。

其实匿名类型是C# 3.0引入的,C# 3.0引入的所有新特性基本都是为了实现LINQ这一伟大的语言特性。匿名类型是为了解决LINQ中选择部分字段以及多字段作为分组依据聚合或是多字段联接的问题的。所以,说白了匿名类型设计的目标就是元组。匿名类型本质上就是关系模型中的元组在C#里面的映射。元组显然是不可变的,匿名类型也没有必要设计成可变的来自找麻烦。

至此,匿名类型的由来就大致讲清楚了,主要是因为工程师要简便优化代码,匠心创作,匿名类型由此诞生。

 

参考文献:【c#入门经典第五版】【知乎】

技术分享图片

友情提示

作者: mhq_martin

博客园地址: http://www.cnblogs.com/mhq-martin/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

 

【C#复习总结】匿名类型由来

标签:强制   stat   private   模型   不可变   class   exception   应该   static   

原文地址:https://www.cnblogs.com/mhq-martin/p/8973686.html


评论


亲,登录后才可以留言!