添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

布尔值类型(bool)

bool类型关键字是.NET System.Boolean结构类型的别名,它表示一个布尔值,可为true或false。

若要使用bool类型的值执行逻辑运算,请使用布尔逻辑运算符。bool类型是比较和相等运算符的结果类型。bool表达式可以是if、do、while和for语句中以及条件运算符 ?: 中的控制条件表达式。

bool类型的默认值为false。

字符类型(char)

char类型关键字是.NET System.Char结构类型的别名,它表示Unicode UTF-16字符。

.NET类型

char类型的默认值为 \0 ,即 U+0000

char类型支持比较、相等、增量和减量运算符。此外,对于char操作数,算数和逻辑位运算符对相应的字符代码执行操作,并得出int类型的结果。

字符串类型将文本表示为char值的序列。

特殊值类型

结构类型(struct)

结构类型( struct type )是一种可封装数据和相关功能的值类型。一般使用 struct 关键词定义其结构。

class Program
    static void Main(string[] args)
        var tesla = new Tesla(2.0, 30.1);
        Console.WriteLine(tesla);
        Console.ReadLine();
public struct Tesla
    public double Width { get; }
    public double Height { get; }
    public Tesla(double width, double height)
        Width = width;
        Height = height;
    public override string ToString() => $"(Width:{Width}, Height:{Height})";

对于struct而言,由于其是值类型,它是在栈(stack)上存储的,可以有效降低内存管理的开销,对于类中包括极小数据的情况,可以考虑使用它。

  • 结构是值类型,在分配内存的时候,速度非常快,因为它们将内联或者保存到栈中,在结构超出作用域被删除时速度也很快
  • 结构体可以把功能相同的数据组织起来,存在一起,用是时候方便,而且在调用函数时,若传递参数较多,传一个结构体相对而言简单一些,很多系统自带的函数必须用结构体。
  • 结构体在使用时可以和枚举一起使用。
  • 结构体成员不能被指定为抽象的、虚拟的、或者保护的对象,因此结构体的成员不能使用如下访问修饰符:abstractvirtualprotected
  • 结构体的函数成员不能声明为abstractvirtual,但是可以使用override关键字,用以覆写它的基类System.ValueType中的方法。
  • 通过New创建结构体对象,必须先初始化所有字段,否则该对象不可用。

    public struct Tesla
        public double Width { get; }
        public double Height { get; }
        public Tesla(double width, double height)
            Width = width;
            //Height = height;
        public override string ToString() => $"(Width:{Width}, Height:{Height})"; 
    

    如果是注释掉构造函数中一个初始化动作,那就会报错。

    结构体和类的区别

  • 当堆栈的空间很有限,且有大量的逻辑对象时,创建类要比创建结构好一些。
  • 对于点、矩形和颜色这样的轻量对象,假如要声明一个含有许多个颜色对象的数组,则CLR需要为每个对象分配内存,在这种情况下,使用结构的成本较低。
  • 在表现抽象和多级别的对象层次时,类是最好的选择,因为结构不支持继承。
  • 大多数情况下,目标类型只是含有一些数据,或者以数据为主,结构体则是最佳选择。
  • 枚举类型(enum)

    枚举类型是由基础整型数值类型的一组命名常量定义的值类型。一般使用enum 关键词定义枚举类型并指定枚举成员。

    enum Season
        Spring,
        Summer,
        Autumn,
        Winter
    

    默认情况下,枚举成员的关联常数值为类型int,它们从0开始,并按定义文本顺序递增1,可以显式指定任何其他整数数值类型作为枚举类型的基础类型,还可以显示指定关联的常数值。

    enum Season
        Spring,
        Summer,
        Autumn = 1000,
        Winter = 2000
    

    注意:不能在枚举类型的定义中定义方法。

    class Program
        static void Main(string[] args)
            Season season = Season.Autumn;
            Console.WriteLine($"{season} value is {(int)season}");
            Console.ReadLine();
    

    使用ushort也可以作为枚举类型的基础类型。

    enum Season : ushort
        Spring,
        Summer,
        Autumn = 1000,
        Winter = 2000
    

    元组类型(ValueTuple)

    元组功能在C# 7.0及更高版本中可用,它提供了简洁的语法,用于将多个数据元素分组成一个轻型数据结构。

    class Program
        static void Main(string[] args)
            (double, int) t1 = (1.2, 5);
            Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}");
            Console.ReadLine();
    

    输出结果是

    Tuple with elements 1.2 and 5
    

    System.ValueTuple类型支持的C#元组不同于System.Tuple类型表示的元组。主要区别如下:

  • System.ValueTuple类型是值类型。System.Tuple类型是引用类型。
  • System.ValueTuple类型是可变的。System.Tuple类型是不可变的。
  • System.ValueTuple类型的数据成员是字段。System.Tuple类型的数据成员是属性。
  • 引用类型的变量存储对其数据(对象)的引用,而值类型的变量直接包含其数据。对于引用类型,两种变量可引用同一对象;因此,对一个变量执行的操作会影响另一个变量所引用的对象。对于值类型,每个变量都具有其自己的数据副本,对一个变量执行的操作不会影响另一个变量(in、ref和out参数变量除外)。

    内置引用类型

    对象类型(object)

    object类型是System.Object在.NET中的别名。

    在C#的统一类型系统中,所有类型(预定义类型、用户定义类型、引用类型和值类型)都是直接或间接从System.Object继承的。可以将任何类型的值赋给object类型的变量。可以使用文本null将任何object变量赋值给其默认值。

  • 将值类型的变量转换为对象的过程称为装箱
  • 将object类型的变量转换为值类型的过程称为取消装箱
  • 字符串类型(string)

    string类型表示零个或多个Unicode字符的序列。string是System.String在.NET中的别名。

    尽管string为引用类型,但是定义相等运算符==!=是为了比较string对象(而不是引用)的值。基于值的相等性使得对字符串相等性的测试更为直观。

    class Program
        static void Main(string[] args)
            string a = "hello";
            string b = "h";
            b += "ello";
            Console.WriteLine(a == b);
            Console.WriteLine(object.ReferenceEquals(a, b));
            Console.ReadLine();
    

    动态类型(dynamic)

    dynamic类型表示变量的使用和对其成员的引用绕过编译时类型检查。改为在运行时解析这些操作。dynamic类型简化了对COM API(例如Office Automation API)、动态API(例如IronPython库)和HTML文档对象模型(DOM)的访问。

    class Program
        static void Main(string[] args)
            dynamic dyn = 1;
            object ojt = 1;
            Console.WriteLine(dyn.GetType());
            Console.WriteLine(ojt.GetType());
            Console.ReadLine();
    

    在大多数情况下,dynamic类型与object类型的行为类似。具体而言,任何非Null表达式都可以转换为dynamic类型。dynamic类型与object的不同之处在于,编译器不会对包含类型dynamic的表达式的操作进行解析或类型检查。编译器将有关该操作信息打包在一起,之后这些信息会用于在运行时评估操作。在此过程中,dynamic类型的变量会编译为object类型的变量。因此,dynamic类型只在编译时存在,在运行时则不存在

    dynamic是从Framework 4.0才有的新特性,它的出现让C#有了弱语言类型的特性。编译器在编译的时候不再对类型进行检查,编译器默认dymanic对象支持你想要的任何特性。

    注意,var关键字和dynamic是有本质区别的,var实际上是编译期的一个语法糖,编译期会自动匹配var变量的实际类型,并用实际类型替代该变量的申明。但是dynamic被编译后,实际上是一个object类型,只不过编译器会对dynamic类型做特殊处理,使它可以在编译期不进行任何的类型检查,而是将类型检查放到运行期。

    dynamic可以简化类型转换。

    dynamic d1 = 7;
    dynamic d2 = "a string";
    dynamic d3 = System.DateTime.Today;
    dynamic d4 = System.Diagnostics.Process.GetProcesses();
    int i = d1;
    string str = d2;
    DateTime dt = d3;
    System.Diagnostics.Process[] procs = d4;
    

    dynamic可以简化反射

    class DynamicExample
        public int Add(int a, int b)
            return a + b;
    class Program
        static void Main(string[] args)
            dynamic dynamicExample = new DynamicExample();
            var result = dynamicExample.Add(1,2);
            Console.WriteLine(result);
            Console.ReadLine();
    

    声明引用类型

    声明类(class)

    使用class关键字声明类。

    class TestClass
        // Methods, properties, fields, events, delegates
        // and nested classes go here.
    

    在C#中仅允许单一继承。也就是说,一个类仅能从一个基类继承实现。但是,一个类可实现多个接口

    类继承和接口实现的一些示例

    接口(interface)

    接口(interface)定义协定。实现该协定的任何class或struct必须提供接口中定义的成员的实现。从C# 8.0开始,接口可为成员定义默认实现。它还可以定义static成员,以便提供常见功能的单个实现。从C# 11开始,接口可以定义static abstractstatic virtual成员来声明实现类型必须提供声明的成员。通常,static virtual方法声明实现必须定义一组重载运算符。

    interface ISampleInterface
        void SampleMethod();
    class ImplementationClass : ISampleInterface
        // Explicit interface member implementation:
        void ISampleInterface.SampleMethod()
            // Method implementation.
        static void Main()
            // Declare an interface instance.
            ISampleInterface obj = new ImplementationClass();
            // Call the member.
            obj.SampleMethod();
    

    接口可以包含如下成员的声明

    实现接口的类可以显式实现该接口的成员。显式实现的成员不能通过类实例访问,而只能通过接口实例访问。此外,只能通过接口实例访问默认接口成员。

    interface IPoint
        // Property signatures:
        int X { get; set; }
        int Y { get; set; }
        double Distance { get; }
    class Point : IPoint
        // Constructor:
        public Point(int x, int y)
            X = x;
            Y = y;
        // Property implementation:
        public int X { get; set; }
        public int Y { get; set; }
        // Property implementation
        public double Distance =>
           Math.Sqrt(X * X + Y * Y);
    class MainClass
        static void PrintPoint(IPoint p)
            Console.WriteLine("x={0}, y={1}", p.X, p.Y);
        static void Main()
            IPoint p = new Point(2, 3);
            Console.Write("My Point: ");
            PrintPoint(p);
    // Output: My Point: x=2, y=3
    

    委托类型(delegate)

    委托类型的声明与方法签名相似。它有一个返回值和任意数目任意类型的参数。

    public delegate void MessageDelegate(string message);
    public delegate int AnotherDelegate(MyType m, long num);
    

    在.NET中,System.ActionSystem.Func类型为许多常见委托提供泛型定义。可能不需要定义新的自定义委托类型。相反,可以创建提供的泛型类型的实例化。

    delegate是一种可用于封装命名方法或匿名方法的引用类型。委托类似于C++中的函数指针;但是,委托是类型安全和可靠的委托是事件的基础。通过将委托与命名方法或匿名方法关联,可以实例化委托

    必须使用具有兼容返回类型和输入参数的方法或lambda表达式实例化委托。为了与匿名方法一起使用,委托和与之关联的代码必须一起声明。

    使用具有协变类型参数的委托

    泛型Func委托中的协变支持的益处。

    // Simple hierarchy of classes.  
    public class Person { }  
    public class Employee : Person { }  
    class Program  
        static Employee FindByTitle(String title)  
            // This is a stub for a method that returns  
            // an employee that has the specified title.  
            return new Employee();  
        static void Test()  
            // Create an instance of the delegate without using variance.  
            Func<String, Employee> findEmployee = FindByTitle;  
            // The delegate expects a method to return Person,  
            // but you can assign it a method that returns Employee.  
            Func<String, Person> findPerson = FindByTitle;  
            // You can also assign a delegate
            // that returns a more derived type
            // to a delegate that returns a less derived type.  
            findPerson = findEmployee;  
    

    使用具有逆变类型参数的委托

    泛型Action委托中的逆变支持的益处

    public class Person { }  
    public class Employee : Person { }  
    class Program  
        static void AddToContacts(Person person)  
            // This method adds a Person object  
            // to a contact list.  
        static void Test()  
            // Create an instance of the delegate without using variance.  
            Action<Person> addPersonToContacts = AddToContacts;  
            // The Action delegate expects
            // a method that has an Employee parameter,  
            // but you can assign it a method that has a Person parameter  
            // because Employee derives from Person.  
            Action<Employee> addEmployeeToContacts = AddToContacts;  
            // You can also assign a delegate
            // that accepts a less derived parameter to a delegate
            // that accepts a more derived parameter.  
            addEmployeeToContacts = addPersonToContacts;  
    
  • Action可用于没有返回值的方法。
  • Func只能用于有返回值的方法。
  • class Program
        public delegate void BuyBook();
        public static void Book()
            Console.WriteLine("我是卖书的");
        static void Main(string[] args)
            BuyBook buyBook = new BuyBook(Book);
            buyBook();
            Console.ReadLine();
    

    这里代表将BuyBook这件事委托给Book函数去执行。

    使用Action可以简化,这样可以简化掉delegate的定义,实现同样的效果。

    class Program
        public static void Book()
            Console.WriteLine("我是卖书的");
        static void Main(string[] args)
            Action buyBook = new Action(Book);
            buyBook();
            Console.ReadLine();
    

    如果我要指定买哪本书呢?难道要一本书一本书的定义,其实没必要,可以使用泛型Action来实现。

    class Program
        public static void Book(string bookName)
            Console.WriteLine($"我是卖书的,我有这本书:{bookName}");
        static void Main(string[] args)
            Action<string> buyBook = new Action<string>(Book);
            buyBook("雷军自传");
            Console.ReadLine();
    

    接下来,不仅要指定买什么书,还要指定在哪家买。

    class Program
        public static void Book(string bookName, string place)
            Console.WriteLine($"我是卖书的,我有这本书:{bookName}, 在{place}");
        static void Main(string[] args)
            Action<string,string> buyBook = new Action<string,string>(Book);
            buyBook("雷军自传","深圳");
            Console.ReadLine();
    

    我们发现,这等于只是查到了哪家有这个书,那么我要直接运行后拿到这本书怎么办,那就需要返回值,这时候我们就可以切换到Func方法。

    class Program
        public static string Book(string bookName, string place)
            return $"我是卖书的,我有这本书:{bookName}, 在{place}, 书送来了";
        static void Main(string[] args)
            Func<string,string,string> buyBook = new Func<string,string,string>(Book);
            var book = buyBook("雷军自传","深圳");
            Console.WriteLine(book);
            Console.ReadLine();
    

    这里还有点不一样的是,当有两个入参的时候,Func实际定义了三个,其中最后一个是定义的返回值。

    Func而言,它是封装了一个不一定具备参数,但是一定返回TResult参数的方法。也就是说,可以没有入参,但是返回是一定有的。

    妙用Func可以从外部给函数内部传递需要实时获取的值。

    class Program
        public static string Book(string bookName, string place)
            return $"在{place}有这本书:{bookName}";
        private static void GoToBuyBook(Func<string,string,string> buyBook)
            var user = "我";
            var book = buyBook("雷军自传","深圳");
            Console.WriteLine($"{user}要去买书,{book}");
        static void Main(string[] args)
            Func<string,string,string> buyBook = new Func<string,string,string>(Book);
            GoToBuyBook(buyBook);
            Console.ReadLine();
    

    在这个示例中,我们可以把一个Func方法作为入参传入到执行函数内部,而实际执行这个方法却是在函数外部,这也是委托的魔力所在。

    除了前面的写法,我们还可以通过Lambda(兰布达)表达式来简化。

    private static void GoToBuyBook(Func<string,string,string> buyBook)
        var user = "我";
        var book = buyBook("雷军自传","深圳");
        Console.WriteLine($"{user}要去买书,{book}");
    static void Main(string[] args)
        Func<string,string,string> buyBook = (bookName,place) =>
            return $"在{place}有这本书:{bookName}";
        GoToBuyBook(buyBook);
        Console.ReadLine();
    
    static void Main(string[] args)
        Action<string,string> buyBook = (bookName,place) =>
            Console.WriteLine($"我是卖书的,我有这本书:{bookName}, 在{place}");
        buyBook("雷军自传","深圳");
        Console.ReadLine();
    

    Task.Run这里可以传入一个无参数的Action委托。

    Task task = Task.Run(()=>{
        Console.WriteLine($"我是卖书的");
    
    public static Task Run(Func<Task?> function);
    public static Task Run(Action action);
    public static Task Run(Action action, CancellationToken cancellationToken);
    

    Task.Factory.StartNew这里可以传入一个无参数的Action委托。

    Task taskFactory = Task.Factory.StartNew(() =>
        Console.WriteLine($"我是卖书的");
    
    public Task StartNew(Action<object?> action, object? state, TaskCreationOptions creationOptions);
    public Task StartNew(Action<object?> action, object? state, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler);
    public Task StartNew(Action<object?> action, object? state);
    public Task StartNew(Action action, TaskCreationOptions creationOptions);
    public Task StartNew(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler);
    public Task StartNew(Action action, CancellationToken cancellationToken);
    public Task StartNew(Action action);
    

    记录(record)

    从C# 9开始,可以使用record关键字定义一个record,用来提供用于封装数据的内置功能。C# 10允许record class语法作为同义词来阐明引用类型,并允许record struct使用相同功能定义值类型。通过使用位置参数或标准属性语法,可以创建具有不可变属性的记录类型

    public record Person(string FirstName, string LastName);
    
    public record Person
        public string FirstName { get; init; } = default!;
        public string LastName { get; init; } = default!;
    
    public readonly record struct Point(double X, double Y, double Z);
    
    public record struct Point
        public double X {  get; init; }
        public double Y {  get; init; }
        public double Z {  get; init; }
    

    还可以创建具有可变属性和字段的记录。

    public record Person
        public string FirstName { get; set; } = default!;
        public string LastName { get; set; } = default!;
    

    记录结构也可以是可变的,包括位置记录结构和没有位置参数的记录结构

    public record struct DataMeasurement(DateTime TakenAt, double Measurement);
    
    public record struct Point
        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; }
    

    记录的主要功能

  • 用于创建具有不可变属性的引用类型的简明语法
  • 内置行为对于以数据为中心的引用类型非常有用:
  • 非破坏性变化的简明语法
  • 用于显示的内置格式设置
  • 支持继承层次结构
  • // 使用record class声明为引用类型记录,class关键字是可选的,当缺省时等价于C#9.0中的record用法
    public record Animal;
    // 等价于
    public record class Animal;
    // 使用record struct声明为结构体类型记录
    public record struct Animal;
    // 也可使用readonly record struct声明为只读结构体类型记录
    public readonly record struct Animal;
    
    // 有构造参数,无其它方法属性等
    public record Animal(string Name, int Age);
    
    public class Animal : IEquatable<Animal>
        public Animal(string Name, int Age)
            this.Name = Name;
            this.Age = Age;
        public string Name { get; init; }
        public int Age { get; init; }
        //其他方法属性
    

    记录允许我们自定义构造方法和属性,但是需要遵循

  • 记录在编译时会根据构造参数生成一个默认的构造函数,默认构造函数不能被覆盖,如果有自定义的构造函数,那么需要使用this关键字初始化这个默认的构造函数。
  • 记录中可以自定义属性,自定义属性名可以与构造参数名重名,也就是说自定义属性可以覆盖构造参数生成的属性,此时对应构造参数将不起任何作用,但是我们可以通过属性指向这个构造参数来自定义这样一个属性。
  • 记录是一个语法糖,本质上还是class或者struct,它只编译时生效,运行时并没有记录这个东西。

    强制转换和类型转换

    由于C#是在编译时静态类型化的,因此变量在声明后就无法再次声明,或无法分配另一种类型的值,除非该类型可以隐式转换为变量的类型。例如,string无法隐式转换为int。因此,在将i声明为int后,无法将字符串“Hello”分配给它,如以下代码所示:

    int i;
    // error CS0029: Cannot implicitly convert type 'string' to 'int'
    i = "Hello";
    

    但有时可能需要将值复制到其他类型的变量或方法参数中。例如,可能需要将一个整数变量传递给参数类型化为double的方法。或者可能需要将类变量分配给接口类型的变量。

    这些类型的操作称为类型转换。在C#中,可以执行以下几种类型的转换:

  • 隐式转换:由于这种转换始终会成功且不会导致数据丢失,因此无需使用任何特殊语法。示例包括从较小整数类型到较大整数类型的转换以及从派生类到基类的转换。
  • // Implicit conversion. A long can
    // hold any value an int can hold, and more!
    int num = 2147483647;
    long bigNum = num;
    
  • 显式转换(强制转换):必须使用强制转换表达式,才能执行显式转换。在转换中可能丢失信息时或在出于其他原因转换可能不成功时,必须进行强制转换。典型的示例包括从数值到精度较低或范围较小的类型的转换和从基类实例到派生类的转换。
  • class Test
        static void Main()
            double x = 1234.7;
            int a;
            // Cast double to int.
            a = (int)x;
            System.Console.WriteLine(a);
    // Output: 1234
    

    用户定义的转换:用户定义的转换是使用特殊方法执行,这些方法可定义为在没有基类和派生类关系的自定义类型之间启用显式转换和隐式转换。

    使用帮助程序类进行转换:若要在非兼容类型(如整数和System.DateTime对象,或十六进制字符串和字节数组)之间转换,可使用System.BitConverter类、System.Convert类和内置数值类型的Parse方法(如Int32.Parse)。

    使用模式匹配以及is和as运算符安全地进行强制转换

    var g = new Giraffe();
    var a = new Animal();
    FeedMammals(g);
    FeedMammals(a);
    // Output:
    // Eating.
    // Animal is not a Mammal
    SuperNova sn = new SuperNova();
    TestForMammals(g);
    TestForMammals(sn);
    static void FeedMammals(Animal a)
        if (a is Mammal m)
            m.Eat();
            // variable 'm' is not in scope here, and can't be used.
            Console.WriteLine($"{a.GetType().Name} is not a Mammal");
    static void TestForMammals(object o)
        // You also can use the as operator and test for null
        // before referencing the variable.
        var m = o as Mammal;
        if (m != null)
            Console.WriteLine(m.ToString());
            Console.WriteLine($"{o.GetType().Name} is not a Mammal");
    // Output:
    // I am an animal.
    // SuperNova is not a Mammal
    class Animal
        public void Eat() { Console.WriteLine("Eating."); }
        public override string ToString()
            return "I am an animal.";
    class Mammal : Animal { }
    class Giraffe : Mammal { }
    class SuperNova { }
    

    装箱和取消装箱

    装箱是将值类型转换为object类型或由此值类型实现的任何接口类型的过程。常见语言运行时(CLR)对值类型进行装箱时,会将值包装在System.Object实例中并将其存储在托管堆中。取消装箱将从对象中提取值类型。装箱是隐式的;取消装箱是显式的。装箱和取消装箱的概念是类型系统C#统一视图的基础,其中任一类型的值都被视为一个对象。

    整型变量i进行了装箱并分配给对象o

    int i = 123;
    // The following line boxes i.
    object o = i;
    

    可以将对象o取消装箱并分配给整型变量i

    o = 123;
    i = (int)o;  // unboxing
    
    // String.Concat example.
    // String.Concat has many versions. Rest the mouse pointer on
    // Concat in the following statement to verify that the version
    // that is used here takes three object arguments. Both 42 and
    // true must be boxed.
    Console.WriteLine(String.Concat("Answer", 42, true));
    // List example.
    // Create a list of objects to hold a heterogeneous collection
    // of elements.
    List<object> mixedList = new List<object>();
    // Add a string element to the list.
    mixedList.Add("First Group:");
    // Add some integers to the list.
    for (int j = 1; j < 5; j++)
        // Rest the mouse pointer over j to verify that you are adding
        // an int to a list of objects. Each element j is boxed when
        // you add j to mixedList.
        mixedList.Add(j);
    // Add another string and more integers.
    mixedList.Add("Second Group:");
    for (int j = 5; j < 10; j++)
        mixedList.Add(j);
    // Display the elements in the list. Declare the loop variable by
    // using var, so that the compiler assigns its type.
    foreach (var item in mixedList)
        // Rest the mouse pointer over item to verify that the elements
        // of mixedList are objects.
        Console.WriteLine(item);
    // The following loop sums the squares of the first group of boxed
    // integers in mixedList. The list elements are objects, and cannot
    // be multiplied or added to the sum until they are unboxed. The
    // unboxing must be done explicitly.
    var sum = 0;
    for (var j = 1; j < 5; j++)
        // The following statement causes a compiler error: Operator
        // '*' cannot be applied to operands of type 'object' and
        // 'object'.
        //sum += mixedList[j] * mixedList[j]);
        // After the list elements are unboxed, the computation does
        // not cause a compiler error.
        sum += (int)mixedList[j] * (int)mixedList[j];
    // The sum displayed is 30, the sum of 1 + 4 + 9 + 16.
    Console.WriteLine("Sum: " + sum);
    // Output:
    // Answer42True
    // First Group:
    // Second Group:
    // Sum: 30
    

    相对于简单的赋值而言,装箱和取消装箱过程需要进行大量的计算。对值类型进行装箱时,必须分配并构造一个新对象。取消装箱所需的强制转换也需要进行大量的计算,只是程度较轻。

    class TestBoxing
        static void Main()
            int i = 123;
            // Boxing copies the value of i into object o.
            object o = i;
            // Change the value of i.
            i = 456;
            // The change in i doesn't affect the value stored in o.
            System.Console.WriteLine("The value-type value = {0}", i);
            System.Console.WriteLine("The object-type value = {0}", o);
    /* Output:
        The value-type value = 456
        The object-type value = 123
    

    取消装箱示例

    class TestUnboxing
        static void Main()
            int i = 123;
            object o = i;  // implicit boxing
                int j = (short)o;  // attempt to unbox
                System.Console.WriteLine("Unboxing OK.");
            catch (System.InvalidCastException e)
                System.Console.WriteLine("{0} Error: Incorrect unboxing.", e.Message);
    

    说个具体的例子,

    class Program
        static void Main(string[] args)
            Console.WriteLine("Number List:{0},{1},{2}",1,2,3);
            Console.WriteLine("Number List:{0},{1},{2}",1.ToString(),2.ToString(),3.ToString());
            Console.ReadLine();
    

    第一行的方式因为从Int值类型要转成string引用类型就存在装箱,第二行这种就直接输入string类型,就减少了装箱的情况。

    将字节数组转换为int

    使用BitConverter类将字节数组转换为int然后又转换回字节数组。例如,在从网络读取字节之后,可能需要将字节转换为内置数据类型。

    byte[] bytes = { 0, 0, 0, 25 };
    // If the system architecture is little-endian (that is, little end first),
    // reverse the byte array.
    if (BitConverter.IsLittleEndian)
        Array.Reverse(bytes);
    int i = BitConverter.ToInt32(bytes, 0);
    Console.WriteLine("int: {0}", i);
    // Output: int: 25
    
    byte[] bytes = BitConverter.GetBytes(201805978);
    Console.WriteLine("byte array: " + BitConverter.ToString(bytes));
    // Output: byte array: 9A-50-07-0C
    

    字符串转换为数字

    你可以调用数值类型(int、long、double等)中找到的Parse或TryParse方法或使用System.Convert类中的方法将string转换为数字。

    调用TryParse方法(例如,int.TryParse("11",outnumber))或Parse方法(例如,varnumber=int.Parse("11"))会稍微高效和简单一些。使用Convert方法对于实现IConvertible的常规对象更有用。

    对预期字符串会包含的数值类型(如System.Int32类型)使用Parse或TryParse方法。Convert.ToInt32方法在内部使用Parse。Parse方法返回转换后的数字;TryParse方法返回布尔值,该值指示转换是否成功,并以out参数形式返回转换后的数字。如果字符串的格式无效,则Parse会引发异常,但TryParse会返回false。调用Parse方法时,应始终使用异常处理来捕获分析操作失败时的FormatException。

    调用Parse或TryParse方法

    Parse和TryParse方法会忽略字符串开头和末尾的空格,但所有其他字符都必须是组成合适数值类型(int、long、ulong、float、decimal等)的字符。如果组成数字的字符串中有任何空格,都会导致错误。例如,可以使用decimal.TryParse分析“10”、“10.3”或“10”,但不能使用此方法分析从“10X”、“10”(注意嵌入的空格)、“10.3”(注意嵌入的空格)、“10e1”(float.TryParse在此处适用)等中分析出10。无法成功分析值为null或String.Empty的字符串。在尝试通过调用String.IsNullOrEmpty方法分析字符串之前,可以检查字符串是否为Null或为空。

    using System;
    public static class StringConversion
        public static void Main()
            string input = String.Empty;
                int result = Int32.Parse(input);
                Console.WriteLine(result);
            catch (FormatException)
                Console.WriteLine($"Unable to parse '{input}'");
            // Output: Unable to parse ''
                int numVal = Int32.Parse("-105");
                Console.WriteLine(numVal);
            catch (FormatException e)
                Console.WriteLine(e.Message);
            // Output: -105
            if (Int32.TryParse("-105", out int j))
                Console.WriteLine(j);
                Console.WriteLine("String could not be parsed.");
            // Output: -105
                int m = Int32.Parse("abc");
            catch (FormatException e)
                Console.WriteLine(e.Message);
            // Output: Input string was not in a correct format.
            const string inputString = "abc";
            if (Int32.TryParse(inputString, out int numValue))
                Console.WriteLine(numValue);
                Console.WriteLine($"Int32.TryParse could not parse '{inputString}' to an int.");
            // Output: Int32.TryParse could not parse 'abc' to an int.
    

    调用Convert方法

    using System;
    public class ConvertStringExample1
        static void Main(string[] args)
            int numVal = -1;
            bool repeat = true;
            while (repeat)
                Console.Write("Enter a number between −2,147,483,648 and +2,147,483,647 (inclusive): ");
                string input = Console.ReadLine();
                // ToInt32 can throw FormatException or OverflowException.
                    numVal = Convert.ToInt32(input);
                    if (numVal < Int32.MaxValue)
                        Console.WriteLine("The new value is {0}", ++numVal);
                        Console.WriteLine("numVal cannot be incremented beyond its current value");
                catch (FormatException)
                    Console.WriteLine("Input string is not a sequence of digits.");
                catch (OverflowException)
                    Console.WriteLine("The number cannot fit in an Int32.");
                Console.Write("Go again? Y/N: ");
                string go = Console.ReadLine();
                if (go.ToUpper() != "Y")
                    repeat = false;
    // Sample Output:
    //   Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive): 473
    //   The new value is 474
    //   Go again? Y/N: y
    //   Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive): 2147483647
    //   numVal cannot be incremented beyond its current value
    //   Go again? Y/N: y
    //   Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive): -1000
    //   The new value is -999
    //   Go again? Y/N: n
    

    在十六进制字符串与数值类型之间转换

    此示例输出string中每个字符的十六进制值。首先,将string分析为字符数组。然后,对每个字符调用ToInt32(Char)获取相应的数值。最后,在string中将数字的格式设置为十六进制表示形式。

    string input = "Hello World!";
    char[] values = input.ToCharArray();
    foreach (char letter in values)
        // Get the integral value of the character.
        int value = Convert.ToInt32(letter);
        // Convert the integer value to a hexadecimal value in string form.
        Console.WriteLine($"Hexadecimal value of {letter} is {value:X}");
    /* Output:
        Hexadecimal value of H is 48
        Hexadecimal value of e is 65
        Hexadecimal value of l is 6C
        Hexadecimal value of l is 6C
        Hexadecimal value of o is 6F
        Hexadecimal value of   is 20
        Hexadecimal value of W is 57
        Hexadecimal value of o is 6F
        Hexadecimal value of r is 72
        Hexadecimal value of l is 6C
        Hexadecimal value of d is 64
        Hexadecimal value of ! is 21
    

    此示例分析十六进制值的string并输出对应于每个十六进制值的字符。首先,调用Split(Char[])方法以获取每个十六进制值作为数组中的单个string。然后,调用ToInt32(String,Int32)将十六进制值转换为表示为int的十进制值。示例中演示了2种不同方法,用于获取对应于该字符代码的字符。第1种方法是使用ConvertFromUtf32(Int32),它将对应于整型参数的字符作为string返回。第2种方法是将int显式转换为char。

    string hexValues = "48 65 6C 6C 6F 20 57 6F 72 6C 64 21";
    string[] hexValuesSplit = hexValues.Split(' ');
    foreach (string hex in hexValuesSplit)
        // Convert the number expressed in base-16 to an integer.
        int value = Convert.ToInt32(hex, 16);
        // Get the character corresponding to the integral value.
        string stringValue = Char.ConvertFromUtf32(value);
        char charValue = (char)value;
        Console.WriteLine("hexadecimal value = {0}, int value = {1}, char value = {2} or {3}",
                            hex, value, stringValue, charValue);
    /* Output:
        hexadecimal value = 48, int value = 72, char value = H or H
        hexadecimal value = 65, int value = 101, char value = e or e
        hexadecimal value = 6C, int value = 108, char value = l or l
        hexadecimal value = 6C, int value = 108, char value = l or l
        hexadecimal value = 6F, int value = 111, char value = o or o
        hexadecimal value = 20, int value = 32, char value =   or
        hexadecimal value = 57, int value = 87, char value = W or W
        hexadecimal value = 6F, int value = 111, char value = o or o
        hexadecimal value = 72, int value = 114, char value = r or r
        hexadecimal value = 6C, int value = 108, char value = l or l
        hexadecimal value = 64, int value = 100, char value = d or d
        hexadecimal value = 21, int value = 33, char value = ! or !
    

    使用类型dynamic

    C# 4引入了一个新类型dynamic。该类型是一种静态类型,但类型为dynamic的对象会跳过静态类型检查。大多数情况下,该对象就像具有类型object一样。在编译时,将假定类型化为dynamic的元素支持任何操作。因此,不必考虑对象是从COMAPI、从动态语言(例如IronPython)、从HTML文档对象模型(DOM)、从反射还是从程序中的其他位置获取自己的值。但是,如果代码无效,则在运行时会捕获到错误。

    例如,如果以下代码中的实例方法exampleMethod1只有一个形参,则编译器会将对该方法的第一个调用ec.exampleMethod1(10,4)识别为无效,因为它包含两个实参。该调用将导致编译器错误。编译器不会检查对该方法的第二个调用dynamic_ec.exampleMethod1(10,4),因为dynamic_ec的类型为dynamic。因此,不会报告编译器错误。但是,该错误不会被无限期疏忽。它将在运行时被捕获,并导致运行时异常。

    static void Main(string[] args)
        ExampleClass ec = new ExampleClass();
        // The following call to exampleMethod1 causes a compiler error
        // if exampleMethod1 has only one parameter. Uncomment the line
        // to see the error.
        //ec.exampleMethod1(10, 4);
        dynamic dynamic_ec = new ExampleClass();
        // The following line is not identified as an error by the
        // compiler, but it causes a run-time exception.
        dynamic_ec.exampleMethod1(10, 4);
        // The following calls also do not cause compiler errors, whether
        // appropriate methods exist or not.
        dynamic_ec.someMethod("some argument", 7, null);
        dynamic_ec.nonexistentMethod();
    class ExampleClass
        public ExampleClass() { }
        public ExampleClass(int v) { }
        public void exampleMethod1(int i) { }
        public void exampleMethod2(string str) { }
    

    在这些示例中,编译器的作用是将有关每个语句的预期作用的信息一起打包到类型化为dynamic的对象或表达式。在运行时,将对存储的信息进行检查,并且任何无效的语句都将导致运行时异常。

    大多数动态操作的结果是其本身dynamic。例如,如果将鼠标指针放在以下示例中使用的testSum上,则IntelliSense将显示类型“(局部变量)dynamictestSum”。

    dynamic d = 1;
    var testSum = d + 3;
    // Rest the mouse pointer over testSum in the following statement.
    System.Console.WriteLine(testSum);
    
  • 结构类型(C# 参考)
  • C# 中的结构类型(struct)
  • C#结构体(Struct)小结(一切尽有,建议收藏!!!)
  • 枚举类型(C# 参考)
  • 【C#】数据类型(sbyte,byte,short,ushort,int,uint,long,ulong和char。、、、)
  • 可为空的值类型(C# 参考)
  • 值类型(C# 参考)
  • 对 Func 和 Action 泛型委托使用变体 (C#)
  • C#之Action和Func的用法
  • c# 委托(Func、Action)
  • 内置引用类型(C# 引用)
  • C#中dynamic的正确用法
  • C#中的动态类型(Dynamic)
  • C#中的记录(record)
  • 装箱和取消装箱(C# 编程指南)
  • C# 如何避免装箱和拆箱操作
  • C# 如何减少装箱拆箱
  • 创建并使用动态对象
  •