[C# Study Notes] Value Type (2)

Insert image description here


Struct structure type

A structure type ("structure type" or "struct type") is a value type that encapsulates data and related functionality. Use the struct keyword to define a structure type:

public struct Coords
{
    
    
    public Coords(double x, double y)
    {
    
    
        X = x;
        Y = y;
    }

    public double X {
    
     get; }
    public double Y {
    
     get; }

    public override string ToString() => $"({
      
      X}, {
      
      Y})";
}

Struct types have value semantics. That is, variables of a structure type contain instances of the type. By default, in assignment, variable values ​​are copied by passing arguments to a method and returning method results. For structure type variables, instances of the type are copied.

Use readonlykeywords to ensure that the structure state is immutable. This ensures that the members within the structure will not modify the state of the structure itself. Precisely because it is a value type, it may be modified, and we do not want it to be modified.

Why is struct not recommended?

Here we also point out why class is often better than struct, because the structure is a value type. On the one hand, the assignment of the structure is achieved by copying the value of the entire structure. This means that when the value of the structure is large, the assignment operation requires copying more data, which may consume a lot of memory and time.

On the other hand, structures are passed by value when passed as parameters to methods. This means that a copy of the structure is passed, rather than the original instance of the structure. This results in modifications to the structure within the method not affecting the original instance.

In contrast, using a class as a reference type avoids the above problems. Assignment and transfer of class objects only involve the copying of references, not the copying of the entire object. This avoids unnecessary consumption of memory and time. Moreover, the transfer of class objects is by reference, which means that modifications to the object within the method will affect the original instance.

The essence of all defects is that the structure is a value type, while class is a reference type.


tuple type

The tuple function provides a concise syntax for grouping multiple data elements into a lightweight data structure. The following example demonstrates how to declare a tuple variable, initialize it, and access its data members:

(double, int) t1 = (4.5, 3);
Console.WriteLine($"Tuple with elements {
      
      t1.Item1} and {
      
      t1.Item2}.");
// Output:
// Tuple with elements 4.5 and 3.

(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {
      
      t2.Count} elements is {
      
      t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.

To define a tuple type, you need to specify the types of all its data members, or alternatively, you can specify the field names. Although you cannot define methods in a tuple type, you can use the methods provided by .NET, as shown in the following example:

(double, int) t = (4.5, 3);
Console.WriteLine(t.ToString());
Console.WriteLine($"Hash code of {
      
      t} is {
      
      t.GetHashCode()}.");
// Output:
// (4.5, 3)
// Hash code of (4.5, 3) is 718460086.

Tuple types are often used when functions accept multiple return values. If you want a mutable data structure with methods, classes are better than tuples.


Nullable value types

In value type variables, most values ​​are not allowed to be null, so we can use Nullable<T>or T?define a nullable value type. But the underlying value type Tcannot itself be a nullable value type.

Nullable value types are typically used when you need to represent undefined values ​​of an underlying value type. For example, a Boolean value or bool variable can only be true or false. However, in some applications, variable values ​​may be undefined or missing. For example, a database field may contain true or false, or it may contain no value, that is, NULL. In this case, the bool? type can be used.

That is to say, when we need a non-nullable value, but in actual situations it may be a null value, we need to useT?

Because value types are implicitly convertible to the corresponding nullable value type, you can assign to a variable of a nullable type just as you would to its underlying value type. A null value can also be assigned. For example:

double? pi = 3.14;
char? letter = 'a';

int m2 = 10;
int? m = m2;

bool? flag = null;

// An array of a nullable value type:
int?[] arr = new int?[10];

The default value representation of a nullable type null, that is, it is an instance of its Nullable<T>.HasValueproperty return false.

There are usually three ways to determine whether a nullable value is empty:

int? a = 42;
if (a is int valueOfA) // valueOfA代表A的ASCII码对应值
{
    
    
}
if (a is null)
{
    
    
}
或者
if (a.HasValue)
{
    
    
}
或者
if (a != null)
{
    
    
}

Convert from nullable value type to underlying type

If you want to assign a value of a nullable value type to a value type variable that is not nullable, you may need to specify a value to assign instead of null.

int? a = 28;
-- 使用??操作符,使用方法是a = x ?? y 或x ??= y
-- a = x??y当x为空,则a=y ,x非空则a= x
-- x??= y当x为空则x=y,非空则不处理
int b = a ?? -1;
Console.WriteLine($"b is {
      
      b}");  // output: b is 28

int? c = null;
int d = c ?? -1;
Console.WriteLine($"d is {
      
      d}");  // output: d is -1

Note that in fact, Tand T?are not the same value type, so if they are both value types, it is okay to use cast, but if you convert a null value to a non-null type, an error will be reported:

int? n = null;

//int m1 = n;    // Doesn't compile
int n2 = (int)n; // Compiles, but throws an exception if n is null

hoisted operator

Any Toperator supported by the type itself T?can run normally if the type is included in the operation. These operators will be promoted and the result of the operation will become nullable, but the type still needs to comply with the Ttype conversion during the operation (for example int+float=浮点型,所以int?+folat?=浮点型?).

int? a = 10;
float? b = null;
double? c = 0;

c = a + b;  // a is null
print(c); --Null

The calculation of bool values ​​is slightly special, and generally conforms to the bool algorithm (I summarized the introduction to Lua in my Lua study notes ) :

bool? a = true;
bool? b = null;
bool? c = true;
c = a & b;
Debug.Log(c); --null
c = a | b;
Debug.Log(c); --true

For comparison operators <、>、<= 和 >=, if one or both operands are null, the result is false; otherwise, the contained values ​​of the operands are compared. nullThe only comparison operation that can be performed with values ==​​is sum !=.

int? a = 10;
Console.WriteLine($"{
      
      a} >= null is {
      
      a >= null}");
Console.WriteLine($"{
      
      a} < null is {
      
      a < null}");
Console.WriteLine($"{
      
      a} == null is {
      
      a == null}");
// Output:
// 10 >= null is False
// 10 < null is False
// 10 == null is False

int? b = null;
int? c = null;
Console.WriteLine($"null >= null is {
      
      b >= c}");
Console.WriteLine($"null == null is {
      
      b == c}");
// Output:
// null >= null is False
// null == null is True

How to determine nullable value types

IsNullable(typeof(T?))

Console.WriteLine($"int? is {
      
      (IsNullable(typeof(int?)) ? "nullable" : "non nullable")} value type");
Console.WriteLine($"int is {
      
      (IsNullable(typeof(int)) ? "nullable" : "non-nullable")} value type");

bool IsNullable(Type type) => Nullable.GetUnderlyingType(type) != null;

// Output:
// int? is nullable value type
// int is non-nullable value type

When obtaining a nullable value type, note that it can only be used typeof()and not used GetType(). The latter can only return the type of the base class:

int? a = 17;
Type typeOfA = a.GetType();
Console.WriteLine(typeOfA.FullName);
// Output:
// System.Int32

In addition, the is keyword cannot determine Tand T?, by default they are of the same type.

int? a = 42;
if (a is int valueOfA)
{
    
    
   print(a); --结果打印 42
}

Why is it recommended to use lessT?

T?Although it is possible to avoid value types accepting null values, we should try to avoid using them T?because this type is actually Tboxing and unboxing of the type. When we declare this variable, it will be boxed by the compiler T?, and when we operate, T?the compiler will unbox it. In fact, it is like a class that has Tand another variable . NullIn order to avoid the impact of packing and unboxing operations on memory, try not to use it if possible.

Boxing and unboxing

Since T?it has been boxed, if we box it again, the following situation will be judged:

  • If HasValuereturned false, a null reference is generated.
  • If HasValueis returned true, the corresponding value of the underlying value type Twill be boxed, but Nullable<T>the instance of will not be boxed. (That is, reboxing Tthe corresponding value of the type)

A boxed value of value type Tcan be unboxed to the corresponding nullable value type T?, as shown in the following example:

int a = 41;
object aBoxed = a; 
int? aNullable = (int?)aBoxed; -- 把已装箱的a取消装箱并重新装箱为int?
Console.WriteLine($"Value of aNullable: {
      
      aNullable}");

object aNullableBoxed = aNullable;   -- HasValue=true,则基础类型int将重新被装箱
if (aNullableBoxed is int valueOfA)
{
    
    
    Console.WriteLine($"aNullableBoxed is boxed int: {
      
      valueOfA}");
}

int? b = null;
object aNullableBoxed = b;   -- HasValue=false,则生成空引用
if (aNullableBoxed == null)
{
    
    
    Console.WriteLine($"aNullableBoxed is boxed int: {
      
      valueOfA}");
}
// Output:
// Value of aNullable: 41
// aNullableBoxed is boxed int: 41
// aNullableBoxed is boxed int: 41

Guess you like

Origin blog.csdn.net/milu_ELK/article/details/132063715