C# 是一种强类型、静态类型的编程语言,这意味着在编译时需要对所有变量进行类型检查。C# 提供了一套丰富且强大的类型系统,使得程序员可以更高效、更安全地进行编程。本文将详细探讨C#的类型系统,包括基础类型、引用类型、复合类型以及类型转换等。

引言

C# 的类型系统是其强大功能和高效性能的基础。理解C#的类型系统对于编写高质量代码至关重要。本文将从基础类型开始,逐步深入探讨引用类型、复合类型、泛型、类型转换、值类型和引用类型的区别,以及如何自定义类型。

基础类型

C# 中的基础类型包括整型、浮点型、字符型、布尔型等。这些类型都继承自 System.ValueType 并且是值类型。

整型

整型用于表示整数,C# 提供了多种整型类型,以适应不同范围的整数需求。

  • byte: 8位无符号整数,范围为 0 到 255。
  • sbyte: 8位有符号整数,范围为 -128 到 127。
  • short: 16位有符号整数,范围为 -32,768 到 32,767。
  • ushort: 16位无符号整数,范围为 0 到 65,535。
  • int: 32位有符号整数,范围为 -2,147,483,648 到 2,147,483,647。
  • uint: 32位无符号整数,范围为 0 到 4,294,967,295。
  • long: 64位有符号整数,范围为 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807。
  • ulong: 64位无符号整数,范围为 0 到 18,446,744,073,709,551,615。

示例代码:

  1. byte byteValue = 255;
  2. sbyte sbyteValue = -128;
  3. short shortValue = 32767;
  4. ushort ushortValue = 65535;
  5. int intValue = 2147483647;
  6. uint uintValue = 4294967295U;
  7. long longValue = 9223372036854775807L;
  8. ulong ulongValue = 18446744073709551615UL;

浮点型

浮点型用于表示带小数点的数值,C# 提供了 floatdouble 两种浮点类型。

  • float: 32位单精度浮点数,范围为 ±1.5 x 10^−45 到 ±3.4 x 10^38,精度约为 7 位十进制数。
  • double: 64位双精度浮点数,范围为 ±5.0 × 10^−324 到 ±1.7 × 10^308,精度约为 15-16 位十进制数。

示例代码:

  1. float floatValue = 3.14F;
  2. double doubleValue = 3.141592653589793;

十进制类型

decimal 类型用于高精度的浮点数运算,通常用于金融和货币计算。它是128位数据类型,精度约为28-29位十进制数。

示例代码:

  1. decimal decimalValue = 79228162514264337593543950335M;

字符型

char 类型用于表示单个字符,占用两个字节,采用 Unicode 编码。

示例代码:

  1. char charValue = 'A';

布尔型

bool 类型用于表示布尔值,只有两个可能的值:truefalse

示例代码:

  1. bool boolValue = true;

引用类型

引用类型存储的是对象的引用,而不是对象本身。引用类型包括类(class)、接口(interface)、数组(array)和委托(delegate)。

类(Class)

类是创建对象的蓝图或模板,定义了对象的属性和行为。类通过关键字 class 定义。

示例代码:

  1. public class Person
  2. {
  3. public string Name { get; set; }
  4. public int Age { get; set; }
  5. public void Introduce()
  6. {
  7. Console.WriteLine($"Hi, I'm {Name} and I'm {Age} years old.");
  8. }
  9. }
  10. Person person = new Person { Name = "Alice", Age = 25 };
  11. person.Introduce(); // 输出 "Hi, I'm Alice and I'm 25 years old."

接口(Interface)

接口是方法和属性的集合,不包含实现。类可以实现接口,从而保证类具有接口定义的功能。

示例代码:

  1. public interface IMovable
  2. {
  3. void Move();
  4. }
  5. public class Car : IMovable
  6. {
  7. public void Move()
  8. {
  9. Console.WriteLine("Car is moving.");
  10. }
  11. }
  12. IMovable movable = new Car();
  13. movable.Move(); // 输出 "Car is moving."

数组(Array)

数组是相同类型数据的集合,使用中括号 [] 定义。数组可以是一维、多维或交错数组。

示例代码:

  1. int[] numbers = { 1, 2, 3, 4, 5 };
  2. foreach (int number in numbers)
  3. {
  4. Console.WriteLine(number);
  5. }

委托(Delegate)

委托是类型安全的函数指针,允许将方法作为参数传递。委托通过关键字 delegate 定义。

示例代码:

  1. public delegate void Notify(string message);
  2. public class Program
  3. {
  4. public static void Main()
  5. {
  6. Notify notify = ShowMessage;
  7. notify("Hello, delegates!");
  8. }
  9. public static void ShowMessage(string message)
  10. {
  11. Console.WriteLine(message);
  12. }
  13. }

复合类型

复合类型是由多个值组合而成的类型,包括结构(struct)、枚举(enum)和元组(tuple)。

结构(Struct)

结构是一种值类型,可以包含多个不同类型的成员。结构通过关键字 struct 定义。

示例代码:

  1. public struct Point
  2. {
  3. public int X { get; set; }
  4. public int Y { get; set; }
  5. public Point(int x, int y)
  6. {
  7. X = x;
  8. Y = y;
  9. }
  10. }
  11. Point point = new Point(10, 20);
  12. Console.WriteLine($"Point: ({point.X}, {point.Y})");

枚举(Enum)

枚举是一种特殊的值类型,用于定义一组命名常量。枚举通过关键字 enum 定义。

示例代码:

  1. public enum DaysOfWeek
  2. {
  3. Sunday,
  4. Monday,
  5. Tuesday,
  6. Wednesday,
  7. Thursday,
  8. Friday,
  9. Saturday
  10. }
  11. DaysOfWeek today = DaysOfWeek.Monday;
  12. Console.WriteLine(today); // 输出 "Monday"

元组(Tuple)

元组是一种轻量级的数据结构,用于存储多个元素。C# 7.0 及以上版本支持内置元组。

示例代码:

  1. var tuple = (Name: "Alice", Age: 25);
  2. Console.WriteLine($"Name: {tuple.Name}, Age: {tuple.Age}");

泛型

泛型允许在类、接口和方法定义时使用类型参数,从而使代码更加通用和类型安全。

泛型类

定义泛型类:

  1. public class GenericList<T>
  2. {
  3. private T[] items = new T[100];
  4. private int count = 0;
  5. public void Add(T item)
  6. {
  7. items[count++] = item;
  8. }
  9. public T Get(int index)
  10. {
  11. return items[index];
  12. }
  13. }
  14. GenericList<int> intList = new GenericList<int>();
  15. intList.Add(1);
  16. intList.Add(2);
  17. Console.WriteLine(intList.Get(1)); // 输出 2

泛型方法

定义泛型方法:

  1. public class Utilities
  2. {
  3. public T Max<T>(T a, T b) where T : IComparable<T>
  4. {
  5. return a.CompareTo(b) > 0 ? a : b;
  6. }
  7. }
  8. Utilities utilities = new Utilities();
  9. int maxInt = utilities.Max(10, 20); // 20
  10. string maxString = utilities.Max("apple", "banana"); // "banana"

类型转换

类型转换是将一种类型的变量转换为另一种类型。C# 支持显式转换和隐式转换。

隐式转换

隐式转换在不丢失数据的情况下自动进行,例如从小范围类型到大范围类型的转换。

示例代码:

  1. int intValue = 10;
  2. double doubleValue = intValue; // 隐式转换

显式转换

显式转换需要使用强制转换运算符 (type),通常用于可能丢失数据或引发异常的转换。

示例代码:

  1. double doubleValue = 10.5
  2. ;
  3. int intValue = (int)doubleValue; // 显式转换

使用 Convert

Convert 类提供了一组静态方法,用于在不同基础类型之间进行转换。

示例代码:

  1. string stringValue = "123";
  2. int intValue = Convert.ToInt32(stringValue); // 使用 Convert 类进行转换

值类型与引用类型的区别

值类型和引用类型的主要区别在于存储位置和赋值方式。

存储位置

  • 值类型存储在栈上,包含实际数据。
  • 引用类型存储在堆上,变量包含对对象的引用。

赋值方式

  • 值类型赋值时复制数据。
  • 引用类型赋值时复制引用。

示例代码:

  1. // 值类型示例
  2. int a = 10;
  3. int b = a; // b 复制了 a 的值
  4. b = 20;
  5. Console.WriteLine(a); // 输出 10
  6. // 引用类型示例
  7. Person person1 = new Person { Name = "Alice" };
  8. Person person2 = person1; // person2 复制了 person1 的引用
  9. person2.Name = "Bob";
  10. Console.WriteLine(person1.Name); // 输出 "Bob"

自定义类型

C# 允许开发者创建自定义类型,以满足特定需求。自定义类型包括类、结构、枚举和接口。

自定义类

定义自定义类:

  1. public class Car
  2. {
  3. public string Make { get; set; }
  4. public string Model { get; set; }
  5. public int Year { get; set; }
  6. public void DisplayInfo()
  7. {
  8. Console.WriteLine($"{Year} {Make} {Model}");
  9. }
  10. }
  11. Car car = new Car { Make = "Toyota", Model = "Camry", Year = 2020 };
  12. car.DisplayInfo(); // 输出 "2020 Toyota Camry"

自定义结构

定义自定义结构:

  1. public struct Rectangle
  2. {
  3. public double Width { get; set; }
  4. public double Height { get; set; }
  5. public double GetArea()
  6. {
  7. return Width * Height;
  8. }
  9. }
  10. Rectangle rect = new Rectangle { Width = 5.0, Height = 10.0 };
  11. Console.WriteLine($"Area: {rect.GetArea()}"); // 输出 "Area: 50"

自定义枚举

定义自定义枚举:

  1. public enum TrafficLight
  2. {
  3. Red,
  4. Yellow,
  5. Green
  6. }
  7. TrafficLight light = TrafficLight.Red;
  8. Console.WriteLine(light); // 输出 "Red"

自定义接口

定义自定义接口:

  1. public interface IAnimal
  2. {
  3. void Speak();
  4. }
  5. public class Dog : IAnimal
  6. {
  7. public void Speak()
  8. {
  9. Console.WriteLine("Woof!");
  10. }
  11. }
  12. IAnimal animal = new Dog();
  13. animal.Speak(); // 输出 "Woof!"

动态类型

dynamic 关键字允许在运行时确定对象的类型,而不是在编译时确定。它提供了更大的灵活性,但会失去编译时类型检查的优势。

使用 dynamic

示例代码:

  1. dynamic obj = 1;
  2. Console.WriteLine(obj); // 输出 1
  3. obj = "Hello, world!";
  4. Console.WriteLine(obj); // 输出 "Hello, world!"
  5. obj = new { Name = "Alice", Age = 25 };
  6. Console.WriteLine(obj.Name); // 输出 "Alice"

类型推断

C# 支持类型推断,即编译器可以根据上下文推断变量的类型。var 关键字用于声明变量时进行类型推断。

使用 var

示例代码:

  1. var number = 10; // 编译器推断 number 的类型为 int
  2. var message = "Hello, world!"; // 编译器推断 message 的类型为 string
  3. Console.WriteLine($"Number: {number}, Message: {message}");

扩展方法

扩展方法允许向现有类型添加新方法,而无需修改类型本身。扩展方法必须在静态类中定义,并且第一个参数使用 this 关键字指定要扩展的类型。

定义扩展方法

示例代码:

  1. public static class StringExtensions
  2. {
  3. public static int WordCount(this string str)
  4. {
  5. return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length;
  6. }
  7. }
  8. string text = "Hello, world!";
  9. int count = text.WordCount();
  10. Console.WriteLine($"Word count: {count}"); // 输出 "Word count: 2"

空值处理

C# 提供了一些工具来处理可能为空的引用类型和值类型。

可空类型

可空类型允许值类型可以为 null,使用 ? 语法表示。

示例代码:

  1. int? nullableInt = null;
  2. if (nullableInt.HasValue)
  3. {
  4. Console.WriteLine($"Value: {nullableInt.Value}");
  5. }
  6. else
  7. {
  8. Console.WriteLine("Value is null");
  9. }

空合并运算符

空合并运算符 ?? 用于简化空值检查和默认值赋值。

示例代码:

  1. string message = null;
  2. string displayMessage = message ?? "Default message";
  3. Console.WriteLine(displayMessage); // 输出 "Default message"

空条件运算符

空条件运算符 ?. 用于安全地调用可能为空的对象成员。

示例代码:

  1. Person person = null;
  2. int? age = person?.Age;
  3. Console.WriteLine($"Age: {age}"); // 输出 "Age: "

类型比较

类型比较是指在运行时检查对象的类型,以便根据类型执行不同的操作。

使用 is 运算符

is 运算符用于检查对象是否为指定类型。

示例代码:

  1. object obj = "Hello, world!";
  2. if (obj is string)
  3. {
  4. Console.WriteLine("obj is a string");
  5. }

使用 as 运算符

as 运算符用于尝试将对象转换为指定类型,如果转换失败,则返回 null

示例代码:

  1. object obj = "Hello, world!";
  2. string str = obj as string;
  3. if (str != null)
  4. {
  5. Console.WriteLine("Conversion succeeded");
  6. }
  7. else
  8. {
  9. Console.WriteLine("Conversion failed");
  10. }

类型转换和模式匹配

C# 提供了多种类型转换和模式匹配的方法,使得代码更简洁、更安全。

模式匹配

C# 7.0 引入了模式匹配语法,允许在 switch 语句和 is 表达式中使用模式匹配。

示例代码:

  1. object obj = "Hello, world!";
  2. if (obj is string str)
  3. {
  4. Console.WriteLine($"String length: {str.Length}");
  5. }
  6. switch (obj)
  7. {
  8. case int i:
  9. Console.WriteLine($"Integer: {i}");
  10. break;
  11. case string s:
  12. Console.WriteLine($"String: {s}");
  13. break;
  14. default:
  15. Console.WriteLine("Unknown type");
  16. break;
  17. }

异常处理

异常处理是捕获和处理运行时错误的重要机制。C# 提供了丰富的异常处理工具,包括自定义异常、异常过滤器等。

自定义异常

自定义异常类继承自 Exception 类。

示例代码:

  1. public class CustomException : Exception
  2. {
  3. public CustomException(string message) : base(message) { }
  4. }
  5. public class Program
  6. {
  7. public static void Main()
  8. {
  9. try
  10. {
  11. throw new CustomException("This is a custom exception.");
  12. }
  13. catch (CustomException ex)
  14. {
  15. Console.WriteLine(ex.Message);
  16. }
  17. }
  18. }

异常过滤器

异常过滤器允许根据条件捕获异常。

示例代码:

  1. try
  2. {
  3. throw new InvalidOperationException("Invalid operation");
  4. }
  5. catch (Exception ex) when (ex.Message.Contains("Invalid"))
  6. {
  7. Console.WriteLine("Caught invalid operation exception");
  8. }

类型推断和异步编程

C# 支持类型推断和异步编程,使代码更加简洁和高效。

异步编程

异步编程允许在不阻塞主线程的情况下执行长时间运行的操作。

示例代码:

  1. using System.Net.Http;
  2. using System.Threading.Tasks;
  3. public class Program
  4. {
  5. public static async Task Main()
  6. {
  7. string url = "http://example.com";
  8. string content = await FetchContentAsync(url);
  9. Console.WriteLine(content);
  10. }
  11. public static async Task<string> FetchContentAsync(string url)
  12. {
  13. using (HttpClient client = new HttpClient())
  14. {
  15. return await client.GetStringAsync(url);
  16. }
  17. }
  18. }

反射

反射是一种在运行时检查和操作类型信息的机制。它允许动态创建对象、调用方法和访问字段和属性。

使用反射获取类型信息

示例代码:

  1. using System;
  2. using System.Reflection;
  3. public class Person
  4. {
  5. public string Name { get; set; }
  6. public int Age { get; set;
  7. }
  8. public void Introduce()
  9. {
  10. Console.WriteLine($"Hi, I'm {Name} and I'm {Age} years old.");
  11. }
  12. }
  13. Type type = typeof(Person);
  14. Console.WriteLine($"Type: {type.Name}");
  15. PropertyInfo[] properties = type.GetProperties();
  16. foreach (PropertyInfo property in properties)
  17. {
  18. Console.WriteLine($"Property: {property.Name}");
  19. }
  20. MethodInfo method = type.GetMethod("Introduce");
  21. Console.WriteLine($"Method: {method.Name}");

动态调用方法

示例代码:

  1. Person person = new Person { Name = "Alice", Age = 25 };
  2. MethodInfo method = typeof(Person).GetMethod("Introduce");
  3. method.Invoke(person, null); // 输出 "Hi, I'm Alice and I'm 25 years old."

总结

C# 的类型系统非常丰富和强大,涵盖了基础类型、引用类型、复合类型和泛型等多种类型。通过理解和掌握C#的类型系统,开发者可以编写出更加健壮、高效和可维护的代码。希望本文能够帮助读者全面了解C#的类型系统,并在实际编程中灵活运用。