C# 高级特性
一、内存管理和垃圾回收 📦
在C#中,由于垃圾收集器机制,内存管理是自动处理的。
垃圾回收 (GC) 是运行时自动回收不再使用的对象所占用的内存的过程,可以防止内存泄漏并促进内存的高效利用。
它自动识别程序不再使用的对象并释放它们占用的内存。虽然垃圾收集器简化了内存管理,但开发人员需要仔细管理不受 .NET 垃圾收集器控制的非托管资源,例如数据库连接或文件流。
栈和堆:在 C# 中,内存分为两个主要区域:栈和堆。栈用于存储值类型和方法调用信息,而堆用于动态分配内存,主要用于引用类型。
如果开发人员不释放这些资源,它们将在应用程序的整个生命周期中持续存在,可能导致内存泄漏和系统压力。
值类型与引用类型:值类型(例如,int,float)存储在栈上,而引用类型(例如,类,数组)分配在堆上。引用类型存储对堆上实际数据的引用。
IDisposable 接口
IDisposable 接口用于释放非托管资源,例如文件句柄、数据库连接或网络套接字。
public class ResourceManager : IDisposable
{
private bool disposed = false; // Track whether resources have been released
private IntPtr handle; // Example: unmanaged resource
private Component component; // Example: managed resource
// Public Dispose method - called through the interface
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // Prevent finalizer from running
}
// Protected virtual Dispose method - allows derived classes to override
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Release managed resources
if (component != null)
{
component.Dispose();
component = null;
}
}
// Release unmanaged resources
if (handle != IntPtr.Zero)
{
Marshal.FreeHGlobal(handle);
handle = IntPtr.Zero;
}
disposed = true;
}
}
~ResourceManager()
{
Dispose(false);
}
// Check if disposed before use
protected void ThrowIfDisposed()
{
if (disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
}
}
using 语句
// Traditional syntax
using (var resource = new ResourceManager())
{
resource.DoWork();
} // Automatically calls Dispose()
// C# 8.0+ simplified syntax
using var resource = new ResourceManager();
resource.DoWork();
// Dispose() is automatically called at the end of the scope
二、文件 IO 操作与序列化 💻
文件输入/输出(I/O)和序列化是 C# 编程的重要 方面,涉及从文件中读取和写入,以及将对象转换为适合存储或传输的格式。
1、文件 I/O 📁
// Reading text from a file
string filePath = "example.txt";
string content = File.ReadAllText(filePath);
// Writing text to a file
string filePath = "example.txt";
string content = "Hello, File I/O!";
File.WriteAllText(filePath, content);
// Reading lines from a file
string filePath = "example.txt";
string[] lines = File.ReadAllLines(filePath);
// Writing lines to a file
string filePath = "example.txt";
string[] lines = { "Line 1", "Line 2", "Line 3" };
File.WriteAllLines(filePath, lines);
using System.IO;
// Define the file path
string filePath = "example.txt";
// Checking if a file exists
bool fileExists = File.Exists(filePath);
if (fileExists)
{
// Deleting a file
File.Delete(filePath);
Console.WriteLine("File deleted successfully.");
}
else
{
Console.WriteLine("File does not exist.");
}
2、序列化 🔗
序列化是将对象或数据结构转换为易于存储或传输的格式的过程。
二进制序列化
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Person person = new Person { Name = "John", Age = 30 };
BinaryFormatter binaryFormatter = new BinaryFormatter();
using (FileStream stream = new FileStream("person.bin", FileMode.Create))
{
binaryFormatter.Serialize(stream, person);
}
二进制反序列化
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// Binary deserialization
BinaryFormatter binaryFormatter = new BinaryFormatter();
using (FileStream stream = new FileStream("person.bin", FileMode.Open))
{
Person deserializedPerson = (Person)binaryFormatter.Deserialize(stream);
Console.WriteLine($"Name: {deserializedPerson.Name}, Age: {deserializedPerson.Age}");
}
JSON 序列化和反序列化
using System.Text.Json;
using System.Text.Json.Serialization;
public class Character
{
// JSON property renaming
[JsonPropertyName("character_name")]
public string Name { get; set; }
public int Level { get; set; }
// Ignore serialization
[JsonIgnore]
public string SecretCode { get; set; }
// Conditional serialization
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Title { get; set; }
// Collection property
public List<Item> Inventory { get; set; }
}
public class Item
{
public string Name { get; set; }
public int Quantity { get; set; }
// Custom serialization converter
[JsonConverter(typeof(CustomPriceConverter))]
public decimal Price { get; set; }
}
public class CustomPriceConverter : JsonConverter<decimal>
{
public override decimal Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
// Custom deserialization logic
return reader.GetDecimal() * 1.1m;
}
public override void Write(
Utf8JsonWriter writer,
decimal value,
JsonSerializerOptions options)
{
// Custom serialization logic
writer.WriteNumberValue(Math.Round(value, 2));
}
}
// JSON serialization manager
public class JsonSerializationManager
{
// Basic serialization
public static string Serialize(object obj)
{
// Configure serialization options
var options = new JsonSerializerOptions
{
WriteIndented = true, // Pretty print
PropertyNameCaseInsensitive = true
};
return JsonSerializer.Serialize(obj, options);
}
// Basic deserialization
public static T? Deserialize<T>(string json)
{
return JsonSerializer.Deserialize<T>(json);
}
// File serialization
public static void SerializeToFile(object obj, string filePath)
{
string json = Serialize(obj);
File.WriteAllText(filePath, json);
}
// Deserialize from file
public static T? DeserializeFromFile<T>(string filePath)
{
string json = File.ReadAllText(filePath);
return Deserialize<T>(json);
}
}
public class Program
{
public static void Main()
{
// Create character
var character = new Character
{
Name = "Hero Allen",
Level = 10,
SecretCode = "HERO123",
Title = "Savior",
Inventory =
[
new() { Name = "Magic Sword", Quantity = 1, Price = 100.50m },
new() { Name = "Healing Potion", Quantity = 5, Price = 20.75m }
]
};
// Basic serialization
string jsonString = JsonSerializationManager.Serialize(character);
Console.WriteLine("Serialization result:");
Console.WriteLine(jsonString);
// Deserialization
var deserializedCharacter = JsonSerializationManager.Deserialize<Character>(jsonString);
Console.WriteLine("\nDeserialization result:");
Console.WriteLine($"Name: {deserializedCharacter?.Name}");
if (deserializedCharacter != null) Console.WriteLine($"Level: {deserializedCharacter.Level}");
// File serialization
string filePath = "character.json";
JsonSerializationManager.SerializeToFile(character, filePath);
Console.WriteLine($"\nSaved to file: {filePath}");
// Deserialize from file
var fileCharacter = JsonSerializationManager.DeserializeFromFile<Character>(filePath);
Console.WriteLine("\nFile deserialization result:");
Console.WriteLine($"Name: {fileCharacter?.Name}");
if (fileCharacter != null) Console.WriteLine($"Item count: {fileCharacter.Inventory.Count}");
}
}
三、委托与事件 👥
委托和事件是 C# 中的基本概念,有助于实现事件驱动编程。它们提供了一种以解耦和可扩展的方式处理和响应事件的方法。
虽然委托本质上是类型安全的函数指针,可以指向一个或多个方法,但事件是一种机制,允许类在发生感兴趣的事情时通知其他类或对象。
1、委托 📝
C# 中的委托是可以指向方法的对象。它们允许以类型安全的方式实现函数指针概念。委托通常用于实现事件和回调。
委托是安全封装方法的类型,并允许定义可保存对这些方法的引用的变量,从而启用回调机制和事件处理。
委托声明
public delegate void MyDelegate(string message);
使用委托
public class MessageProcessor
{
public void ProcessMessage(string message)
{
Console.WriteLine($"Processing message: {message}");
}
}
// Delegate declaration
public delegate void MyDelegate(string message);
public class Program
{
public static void Main()
{
MessageProcessor processor = new MessageProcessor();
MyDelegate delegateInstance = processor.ProcessMessage;
// Invoking the delegate
delegateInstance("Hello, Delegate!");
}
}
多播委托
允许一个委托实例持有多个方法的引用,当调用该委托时,所有被引用的方法都会按照添加顺序依次执行。
这可能会增加管理和调试的复杂性,如果处理不当,可能会导致意想不到的副作用。
public class Calculator
{
public void Add(int a, int b)
{
Console.WriteLine($"Sum: {a + b}");
}
public void Multiply(int a, int b)
{
Console.WriteLine($"Product: {a * b}");
}
}
// Delegate declaration
public delegate void MyDelegate(int a, int b);
public class Program
{
public static void Main()
{
Calculator calculator = new Calculator();
MyDelegate addDelegate = calculator.Add;
MyDelegate multiplyDelegate = calculator.Multiply;
// Creating a multicast delegate
MyDelegate multicastDelegate = addDelegate + multiplyDelegate;
// Invoking both methods
multicastDelegate(3, 4);
}
}
内置泛型委托类型
在 C# 中,Func<T> 用于返回值的委托,Action<T> 用于不返回值的委托,Predicate<T> 用于返回布尔值的委托。
Func<int, int, int> add = (a, b) => a + b;
int result = add(5, 3); // result = 8
// Calculate damage
Func<int, float, int> calculateDamage = (baseDamage, criticalMultiplier) =>
(int)(baseDamage * criticalMultiplier);
Action<string> logMessage = message => Console.WriteLine(message);
logMessage("Player attacked!");
// Character action
Action<Character> playerAttack = character => {
character.TakeDamage(10);
character.PlayAttackAnimation();
};
Predicate<int> isEven = number => number % 2 == 0;
bool result = isEven(4); // true
// Enemy filtering
Predicate<Enemy> isLowHealth = enemy => enemy.Health < 20;
List<Enemy> weakEnemies = enemyList.FindAll(isLowHealth);
2、事件 📣
事件使用委托来通知状态更改,允许一个对象通知其他对象有关某些事件的信息。
事件声明
public class EventPublisher
{
public event MyDelegate MyEvent;
}
订阅事件
public class EventSubscriber
{
public void Subscribe(EventPublisher publisher)
{
publisher.MyEvent += HandleEvent;
}
public void HandleEvent(string message)
{
Console.WriteLine($"Event handled: {message}");
}
}
发布事件
EventPublisher publisher = new EventPublisher();
EventSubscriber subscriber = new EventSubscriber();
subscriber.Subscribe(publisher);
publisher.MyEvent?.Invoke("Event message"); // Raising the event
事件处理程序和 EventArgs
public class CustomEventArgs : EventArgs
{
public string EventMessage { get; }
public CustomEventArgs(string message)
{
EventMessage = message;
}
}
public class EventPublisher
{
public event EventHandler<CustomEventArgs> MyEvent;
public void RaiseEvent(string message)
{
OnMyEvent(new CustomEventArgs(message));
}
protected virtual void OnMyEvent(CustomEventArgs e)
{
MyEvent?.Invoke(this, e);
}
}
3、综合例子 📚
// Define delegate types
public delegate void AttackDelegate(string target);
public delegate int CalculateDamageDelegate(int baseAttack, int criticalChance);
public class GameCharacter(string name, int health, int attack)
{
// Event delegates
public event AttackDelegate? OnAttack;
public event Action<string>? OnDeath;
public string Name { get; } = name;
private int Health { get; set; } = health;
private int Attack { get; } = attack;
// Perform attack using delegate
public void PerformAttack(string target, CalculateDamageDelegate damageCalculator)
{
// Calculate damage
int damage = damageCalculator(Attack, 20);
// Trigger attack event
OnAttack?.Invoke(target);
Console.WriteLine($"{Name} attacks {target}, dealing {damage} damage");
}
public void TakeDamage(int damage)
{
Health -= damage;
if (Health <= 0)
{
// Trigger death event
OnDeath?.Invoke($"{Name} has fallen");
}
}
}
public abstract class SkillManager
{
// Multicast delegate
public delegate void SkillEventHandler(string skillName);
public static event SkillEventHandler? OnSkillUsed;
public static void UseSkill(string skillName, Action? skillAction)
{
// Trigger skill event
OnSkillUsed?.Invoke(skillName);
// Execute skill action
skillAction?.Invoke();
}
}
public class CombatLogger
{
public void LogAttack(string target)
{
Console.WriteLine($"[Log] Attack target: {target}");
}
public void LogDeath(string deathMessage)
{
Console.WriteLine($"[Log] {deathMessage}");
}
}
public class Program
{
public static void Main()
{
GameCharacter hero = new GameCharacter("Hero", 100, 20);
GameCharacter monster = new GameCharacter("Goblin", 50, 10);
CombatLogger logger = new CombatLogger();
// Register events
hero.OnAttack += logger.LogAttack;
hero.OnDeath += logger.LogDeath;
monster.OnDeath += logger.LogDeath;
// Register skill event
SkillManager.OnSkillUsed += skillName =>
Console.WriteLine($"[Skill] Used skill: {skillName}");
// Define damage calculation delegate
CalculateDamageDelegate standardDamage = (baseAttack, criticalChance) =>
{
Random random = new Random();
bool isCritical = random.Next(100) < criticalChance;
return isCritical ? baseAttack * 2 : baseAttack;
};
SkillManager.UseSkill("Fireball", () =>
{
Console.WriteLine("Casting a powerful fireball!");
});
hero.PerformAttack(monster.Name, standardDamage);
monster.TakeDamage(25);
// Func and Action delegate examples
Func<int, int, int> multiply = (a, b) => a * b;
Action<string> print = Console.WriteLine;
Console.WriteLine($"Multiplication result: {multiply(5, 3)}");
print("Delegate usage example");
}
}
五、基本原则 🔝
- 可读性:代码应该易于阅读和理解,不仅对于创建者而且对于其他开发人员。这有利于更顺畅的协作和知识转移。
- 模块化:代码应分为可独立测试和使用的逻辑块或模块。这种方法有助于隔离问题并增强代码的可重用性。
- 不要重复 (DRY) :通过利用提高代码可重用性的方法、函数和类来避免代码重复,这有助于更轻松地维护代码并减少出现错误的可能性。
- 清晰的接口:函数和类应该具有清晰且易于理解的接口,以促进不同代码组件之间更好的交互和集成。
- 简单性:避免复杂而复杂的结构,这会使代码维护更具挑战性。力求简单和清晰,使代码更易于理解和修改。
六、设计模式 👥
设计模式是针对软件设计中遇到的常见问题的可重用解决方案。它们提供了一种结构化的方法来解决某些类型的问题,并促进代码更加模块化、可维护和可扩展。
在 C# 中,我经常使用单例、工厂、观察者、策略和装饰者等模式。
1、单例模式 📝
单例模式确保一个类在整个系统中只有一个实例,并提供对该实例的全局访问点。这对于管理应限制为单个实例的资源非常有用,确保一致性并防止资源使用中的潜在冲突。
C# 中的单例模式可以使用类的静态实例与私有构造函数相结合来实现。要注意的主要问题之一是多线程环境,这可能会导致创建多个实例。这可以通过采用锁定机制或延迟初始化来规避。
public class Singleton
{
private static Singleton instance;
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}