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。
示例代码:
byte byteValue = 255;
sbyte sbyteValue = -128;
short shortValue = 32767;
ushort ushortValue = 65535;
int intValue = 2147483647;
uint uintValue = 4294967295U;
long longValue = 9223372036854775807L;
ulong ulongValue = 18446744073709551615UL;
浮点型
浮点型用于表示带小数点的数值,C# 提供了 float
和 double
两种浮点类型。
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 位十进制数。
示例代码:
float floatValue = 3.14F;
double doubleValue = 3.141592653589793;
十进制类型
decimal
类型用于高精度的浮点数运算,通常用于金融和货币计算。它是128位数据类型,精度约为28-29位十进制数。
示例代码:
decimal decimalValue = 79228162514264337593543950335M;
字符型
char
类型用于表示单个字符,占用两个字节,采用 Unicode 编码。
示例代码:
char charValue = 'A';
布尔型
bool
类型用于表示布尔值,只有两个可能的值:true
和 false
。
示例代码:
bool boolValue = true;
引用类型
引用类型存储的是对象的引用,而不是对象本身。引用类型包括类(class)、接口(interface)、数组(array)和委托(delegate)。
类(Class)
类是创建对象的蓝图或模板,定义了对象的属性和行为。类通过关键字 class
定义。
示例代码:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void Introduce()
{
Console.WriteLine($"Hi, I'm {Name} and I'm {Age} years old.");
}
}
Person person = new Person { Name = "Alice", Age = 25 };
person.Introduce(); // 输出 "Hi, I'm Alice and I'm 25 years old."
接口(Interface)
接口是方法和属性的集合,不包含实现。类可以实现接口,从而保证类具有接口定义的功能。
示例代码:
public interface IMovable
{
void Move();
}
public class Car : IMovable
{
public void Move()
{
Console.WriteLine("Car is moving.");
}
}
IMovable movable = new Car();
movable.Move(); // 输出 "Car is moving."
数组(Array)
数组是相同类型数据的集合,使用中括号 []
定义。数组可以是一维、多维或交错数组。
示例代码:
int[] numbers = { 1, 2, 3, 4, 5 };
foreach (int number in numbers)
{
Console.WriteLine(number);
}
委托(Delegate)
委托是类型安全的函数指针,允许将方法作为参数传递。委托通过关键字 delegate
定义。
示例代码:
public delegate void Notify(string message);
public class Program
{
public static void Main()
{
Notify notify = ShowMessage;
notify("Hello, delegates!");
}
public static void ShowMessage(string message)
{
Console.WriteLine(message);
}
}
复合类型
复合类型是由多个值组合而成的类型,包括结构(struct)、枚举(enum)和元组(tuple)。
结构(Struct)
结构是一种值类型,可以包含多个不同类型的成员。结构通过关键字 struct
定义。
示例代码:
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
Point point = new Point(10, 20);
Console.WriteLine($"Point: ({point.X}, {point.Y})");
枚举(Enum)
枚举是一种特殊的值类型,用于定义一组命名常量。枚举通过关键字 enum
定义。
示例代码:
public enum DaysOfWeek
{
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
DaysOfWeek today = DaysOfWeek.Monday;
Console.WriteLine(today); // 输出 "Monday"
元组(Tuple)
元组是一种轻量级的数据结构,用于存储多个元素。C# 7.0 及以上版本支持内置元组。
示例代码:
var tuple = (Name: "Alice", Age: 25);
Console.WriteLine($"Name: {tuple.Name}, Age: {tuple.Age}");
泛型
泛型允许在类、接口和方法定义时使用类型参数,从而使代码更加通用和类型安全。
泛型类
定义泛型类:
public class GenericList<T>
{
private T[] items = new T[100];
private int count = 0;
public void Add(T item)
{
items[count++] = item;
}
public T Get(int index)
{
return items[index];
}
}
GenericList<int> intList = new GenericList<int>();
intList.Add(1);
intList.Add(2);
Console.WriteLine(intList.Get(1)); // 输出 2
泛型方法
定义泛型方法:
public class Utilities
{
public T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b;
}
}
Utilities utilities = new Utilities();
int maxInt = utilities.Max(10, 20); // 20
string maxString = utilities.Max("apple", "banana"); // "banana"
类型转换
类型转换是将一种类型的变量转换为另一种类型。C# 支持显式转换和隐式转换。
隐式转换
隐式转换在不丢失数据的情况下自动进行,例如从小范围类型到大范围类型的转换。
示例代码:
int intValue = 10;
double doubleValue = intValue; // 隐式转换
显式转换
显式转换需要使用强制转换运算符 (type)
,通常用于可能丢失数据或引发异常的转换。
示例代码:
double doubleValue = 10.5
;
int intValue = (int)doubleValue; // 显式转换
使用 Convert
类
Convert
类提供了一组静态方法,用于在不同基础类型之间进行转换。
示例代码:
string stringValue = "123";
int intValue = Convert.ToInt32(stringValue); // 使用 Convert 类进行转换
值类型与引用类型的区别
值类型和引用类型的主要区别在于存储位置和赋值方式。
存储位置
- 值类型存储在栈上,包含实际数据。
- 引用类型存储在堆上,变量包含对对象的引用。
赋值方式
- 值类型赋值时复制数据。
- 引用类型赋值时复制引用。
示例代码:
// 值类型示例
int a = 10;
int b = a; // b 复制了 a 的值
b = 20;
Console.WriteLine(a); // 输出 10
// 引用类型示例
Person person1 = new Person { Name = "Alice" };
Person person2 = person1; // person2 复制了 person1 的引用
person2.Name = "Bob";
Console.WriteLine(person1.Name); // 输出 "Bob"
自定义类型
C# 允许开发者创建自定义类型,以满足特定需求。自定义类型包括类、结构、枚举和接口。
自定义类
定义自定义类:
public class Car
{
public string Make { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public void DisplayInfo()
{
Console.WriteLine($"{Year} {Make} {Model}");
}
}
Car car = new Car { Make = "Toyota", Model = "Camry", Year = 2020 };
car.DisplayInfo(); // 输出 "2020 Toyota Camry"
自定义结构
定义自定义结构:
public struct Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
public double GetArea()
{
return Width * Height;
}
}
Rectangle rect = new Rectangle { Width = 5.0, Height = 10.0 };
Console.WriteLine($"Area: {rect.GetArea()}"); // 输出 "Area: 50"
自定义枚举
定义自定义枚举:
public enum TrafficLight
{
Red,
Yellow,
Green
}
TrafficLight light = TrafficLight.Red;
Console.WriteLine(light); // 输出 "Red"
自定义接口
定义自定义接口:
public interface IAnimal
{
void Speak();
}
public class Dog : IAnimal
{
public void Speak()
{
Console.WriteLine("Woof!");
}
}
IAnimal animal = new Dog();
animal.Speak(); // 输出 "Woof!"
动态类型
dynamic
关键字允许在运行时确定对象的类型,而不是在编译时确定。它提供了更大的灵活性,但会失去编译时类型检查的优势。
使用 dynamic
示例代码:
dynamic obj = 1;
Console.WriteLine(obj); // 输出 1
obj = "Hello, world!";
Console.WriteLine(obj); // 输出 "Hello, world!"
obj = new { Name = "Alice", Age = 25 };
Console.WriteLine(obj.Name); // 输出 "Alice"
类型推断
C# 支持类型推断,即编译器可以根据上下文推断变量的类型。var
关键字用于声明变量时进行类型推断。
使用 var
示例代码:
var number = 10; // 编译器推断 number 的类型为 int
var message = "Hello, world!"; // 编译器推断 message 的类型为 string
Console.WriteLine($"Number: {number}, Message: {message}");
扩展方法
扩展方法允许向现有类型添加新方法,而无需修改类型本身。扩展方法必须在静态类中定义,并且第一个参数使用 this
关键字指定要扩展的类型。
定义扩展方法
示例代码:
public static class StringExtensions
{
public static int WordCount(this string str)
{
return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
string text = "Hello, world!";
int count = text.WordCount();
Console.WriteLine($"Word count: {count}"); // 输出 "Word count: 2"
空值处理
C# 提供了一些工具来处理可能为空的引用类型和值类型。
可空类型
可空类型允许值类型可以为 null
,使用 ?
语法表示。
示例代码:
int? nullableInt = null;
if (nullableInt.HasValue)
{
Console.WriteLine($"Value: {nullableInt.Value}");
}
else
{
Console.WriteLine("Value is null");
}
空合并运算符
空合并运算符 ??
用于简化空值检查和默认值赋值。
示例代码:
string message = null;
string displayMessage = message ?? "Default message";
Console.WriteLine(displayMessage); // 输出 "Default message"
空条件运算符
空条件运算符 ?.
用于安全地调用可能为空的对象成员。
示例代码:
Person person = null;
int? age = person?.Age;
Console.WriteLine($"Age: {age}"); // 输出 "Age: "
类型比较
类型比较是指在运行时检查对象的类型,以便根据类型执行不同的操作。
使用 is
运算符
is
运算符用于检查对象是否为指定类型。
示例代码:
object obj = "Hello, world!";
if (obj is string)
{
Console.WriteLine("obj is a string");
}
使用 as
运算符
as
运算符用于尝试将对象转换为指定类型,如果转换失败,则返回 null
。
示例代码:
object obj = "Hello, world!";
string str = obj as string;
if (str != null)
{
Console.WriteLine("Conversion succeeded");
}
else
{
Console.WriteLine("Conversion failed");
}
类型转换和模式匹配
C# 提供了多种类型转换和模式匹配的方法,使得代码更简洁、更安全。
模式匹配
C# 7.0 引入了模式匹配语法,允许在 switch
语句和 is
表达式中使用模式匹配。
示例代码:
object obj = "Hello, world!";
if (obj is string str)
{
Console.WriteLine($"String length: {str.Length}");
}
switch (obj)
{
case int i:
Console.WriteLine($"Integer: {i}");
break;
case string s:
Console.WriteLine($"String: {s}");
break;
default:
Console.WriteLine("Unknown type");
break;
}
异常处理
异常处理是捕获和处理运行时错误的重要机制。C# 提供了丰富的异常处理工具,包括自定义异常、异常过滤器等。
自定义异常
自定义异常类继承自 Exception
类。
示例代码:
public class CustomException : Exception
{
public CustomException(string message) : base(message) { }
}
public class Program
{
public static void Main()
{
try
{
throw new CustomException("This is a custom exception.");
}
catch (CustomException ex)
{
Console.WriteLine(ex.Message);
}
}
}
异常过滤器
异常过滤器允许根据条件捕获异常。
示例代码:
try
{
throw new InvalidOperationException("Invalid operation");
}
catch (Exception ex) when (ex.Message.Contains("Invalid"))
{
Console.WriteLine("Caught invalid operation exception");
}
类型推断和异步编程
C# 支持类型推断和异步编程,使代码更加简洁和高效。
异步编程
异步编程允许在不阻塞主线程的情况下执行长时间运行的操作。
示例代码:
using System.Net.Http;
using System.Threading.Tasks;
public class Program
{
public static async Task Main()
{
string url = "http://example.com";
string content = await FetchContentAsync(url);
Console.WriteLine(content);
}
public static async Task<string> FetchContentAsync(string url)
{
using (HttpClient client = new HttpClient())
{
return await client.GetStringAsync(url);
}
}
}
反射
反射是一种在运行时检查和操作类型信息的机制。它允许动态创建对象、调用方法和访问字段和属性。
使用反射获取类型信息
示例代码:
using System;
using System.Reflection;
public class Person
{
public string Name { get; set; }
public int Age { get; set;
}
public void Introduce()
{
Console.WriteLine($"Hi, I'm {Name} and I'm {Age} years old.");
}
}
Type type = typeof(Person);
Console.WriteLine($"Type: {type.Name}");
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
Console.WriteLine($"Property: {property.Name}");
}
MethodInfo method = type.GetMethod("Introduce");
Console.WriteLine($"Method: {method.Name}");
动态调用方法
示例代码:
Person person = new Person { Name = "Alice", Age = 25 };
MethodInfo method = typeof(Person).GetMethod("Introduce");
method.Invoke(person, null); // 输出 "Hi, I'm Alice and I'm 25 years old."
总结
C# 的类型系统非常丰富和强大,涵盖了基础类型、引用类型、复合类型和泛型等多种类型。通过理解和掌握C#的类型系统,开发者可以编写出更加健壮、高效和可维护的代码。希望本文能够帮助读者全面了解C#的类型系统,并在实际编程中灵活运用。