zoukankan      html  css  js  c++  java
  • 重学c#————struct

    前言

    简单整理一下struct。

    正文

    struct

    对于struct 而言呢,我们往往会拿class作为对比,但是呢,我们在初学阶段用class来替代struct,struct的存在感越来越低了。

    那么是什么原因使我们经常使用struct呢?我感觉很简单的一句话就是struct能做的class都能做,struct不能做的,class 也能做,这就是问题关键了。

    那么先来看下他们的对比:

    1、结构是值类型,它在栈中分配空间;而类是引用类型,它在堆中分配空间,栈中保存的只是引用。

    2、结构类型直接存储成员数据,让其他类的数据位于堆中,位于栈中的变量保存的是指向堆中数据对象的引用。

    1. 结构不支持继承。

    2. 结构不能声明默认的构造函数。

    3. 结构类型中不能设置默认值。

    从第二点中可以明白结构类型中,不一定存储的一定是值,还可能是引用,这就打破了初学的时候误以为结构类型只能存储值类型,还可能是引用对象的引用,如下:

    static void Main(string[] args)
    {
    	var parent = new Parent(30,"张大大");
    	var zhangsan = new Student(1,"张三",parent);
    	zhangsan.age = 10;
    }
    struct Student {
    	public Student(int age, string name,Parent parent)
    	{
    		this.age = age;
    		this.name = name;
    		this.parent = parent;
    	}
    	public int age { get; set; }
    	public string name { get; set; }
       
    	public Parent parent { get; set; }
    }
    
    struct Parent {
    	public Parent(int age, string name)
    	{
    		this.age = age;
    		this.name = name;
    	}
    	public int age { get; set; }
    	public string name { get; set; }
    }
    

    在Student 结构中,我们也可以去复制引用。

    第三点很好理解,第四点表示我们不能去自己声明默认构造函数。如:

    public Student() { 
    }
    

    那么我们什么时候使用struct呢?

    那么要从struct 优点出发,struct 是值类型,当离开作用域的时候,那么对垃圾回收是有好处的。

    同样,因为struct 是值类型,分配到堆上,如果值类型过大,这会大量占用到堆的空间,所以我们的数据比较下。

    当有大量的赋值语句的时候,那么我们也应该避开struct,因为赋值值类型中将会拷贝全部,而不是引用。

    根据上诉,实用场景为:

    对于点、矩形和颜色这样的轻量对象,假如要声明一个含有许多个颜色对象的数组,则CLR需要为每个对象分配内存,在这种情况下,使用结构的成本较低;

    从上总结出,struct可以在一些以数据为主的场景中使用,且数据量不大的情况。

    struct 作为参数

    在介绍readonly 之前,先介绍一下,和ref 还有out 其名的in,不是别的in哈。

    static void Main(string[] args)
    {
    	int readonlyArgument = 44;
    	InArgExample(readonlyArgument);
    	Console.WriteLine(readonlyArgument);     // value is still 44
    }
    static void InArgExample(in int number)
    {
    	// Uncomment the following line to see error CS8331
    	//number = 19;
    }
    

    这里的in 的作用是可以引用readonlyArgument,但是只读,不能修改number的值。那么这有什么用呢?我直接不设置值不就可以吗?或者说我起码设置一个readonly 这总行吧。

    而我们知道in 有不能用于异步方法,赋值消耗也不大形参,那我要这个引用有啥用?关键就在于我们自定义的struct还是大有好处的,struct 是我们自定义的结构类型,这个比较大,那么这就是一个struct的突破点了,传值的时候可以传递struct。

    下面介绍readonly 这个是为了安全,做为一个readonly,我们首先就要区分的是const,const 是编译性,而readonly是运行时。这个可以百度,在此就不做过多的介绍。

    通过readonly struct 还有 in,那么可以创建防御性副本。

    这里值得注意的是,官网提到这样一句话:

    除非使用 readonly 修饰符声明 struct或方法仅调用该结构的 readonly 成员,否则切勿将其作为 in 参数传递。 不遵守该指南可能会对性能产生负面影响,并可能导致不明确的行为
    

    官网给出了一个这样的例子:

    int readonlyArgument = 44;
    InArgExample(readonlyArgument);
    Console.WriteLine(readonlyArgument);     // value is still 44
    
    void InArgExample(in int number)
    {
        // Uncomment the following line to see error CS8331
        //number = 19;
    }
    

    并且说明了一段这样的话:

    在首次检查时,你可能认为这些访问是安全的。 毕竟,get 访问器不应该修改对象的状态。 但是没有强制执行的语言规则。 
    它只是通用约定。 任何类型都可以实现修改内部状态的 get 访问器。 
    如果没有语言保证,编译器必须在调用任何未标记为 readonly 修饰符的成员之前创建参数的临时副本。 
    在堆栈上创建临时存储,将参数的值复制到临时存储中,并将每个成员访问的值作为 this 参数复制到堆栈中。 
    在许多情况下,当参数类型不是 readonly struct,并且该方法调用成员未标记为 readonly 时,这些副本会降低性能,
    使得按值传递比按只读引用传递速度更快。 如果将不修改结构状态的所有方法标记为 readonly,编译器就可以安全地确定不修改结构状态,并且不需要防御性复制。
    

    struct 作为出参

    那么上面讨论了参数传递的问题,那么接下来讨论一下,参数返回的问题。

    比如说返回了:

    var a=getANumber();
    private static int getANumber(){
       var b=1;
       return b;
    }
    

    那么其实这个a的值怎么获取的呢?是b的值赋值给a。

    但是对于比较大的struct,用这种赋值的方式,就比较消耗cpu和内存了。

    那么可以使用ref来返回。

    var a=getANumber();
    private static ref int getANumber(){
       var b=1;
       return ref b;
    }
    

    这样b的引用传递给了a。

    如果你希望返回的参数不可改变,那么你可以这样:

    var a=getANumber();
    private static ref readonly int getANumber(){
       var b=1;
       return ref b;
    }
    

    那么这个时候有人就奇怪了,为啥ref还要 readonly 这东西呢?

    举个例子:

    public static ref int Find(int[,] matrix, Func<int, bool> predicate)
    {
        for (int i = 0; i < matrix.GetLength(0); i++)
            for (int j = 0; j < matrix.GetLength(1); j++)
                if (predicate(matrix[i, j]))
                    return ref matrix[i, j];
        throw new InvalidOperationException("Not found");
    }
    

    这个例子返回的是数组的一部分,如果改了这个值,那么数组里面的值不就改变了吗。

    可能我这样说,加上官网这个例子不到位,可能没能表达明白。再来一个自己写的例子:

    static void Main(string[] args)
    {
    	var matrix =new Student[1];
    	matrix[0] = new Student(20,"张三",new Parent());
    	ref Student result =ref Find(matrix);
    	result.age = 10;
    	Console.WriteLine(matrix[0].age);
    	Console.WriteLine(result.age);
    	Console.ReadLine();
    }
    
    static ref Student Find(Student[] matrix )
    {
    	return ref matrix[0];
    }
    struct Student {
    	public Student(int age, string name,Parent parent)
    	{
    		this.age = age;
    		this.name = name;
    		this.parent = parent;
    	}
    	public int age { get; set; }
    	public string name { get; set; }
       
    	public Parent parent { get; set; }
    }
    

    这里打印出来两个都是10。

    如果给Find 加上readonly,那么要这样写

    ref readonly Student result =ref Find(matrix);

    Student 自然不能再进行赋值。

    上述只是个人整理和一点点个人理解,如有不对望指出。下一节,整理c# 装箱和拆箱,介绍一下生命周期。

  • 相关阅读:
    XmlDocument和XDocument转String
    C# 6.0 (C# vNext) 新功能之:Null-Conditional Operator(转)
    <%%>与<scriptrunat=server>,<%=%>与<%#%>的区别(转)
    WINDOWS的用户和用户组说明
    C#获取网页内容的三种方式(转)
    C#操作XML方法:新增、修改和删除节点与属性
    linq to xml操作XML(转)
    C#使用tesseract3.02识别验证码模拟登录(转)
    C#修饰符
    SQL语句备份和还原数据库(转)
  • 原文地址:https://www.cnblogs.com/aoximin/p/14403473.html
Copyright © 2011-2022 走看看