YGFramework游戏框架文档
YGFramework游戏框架文档
我为什么要写这套框架#
大一大二时候参加了多个未来制作人大赛和48小时极限开发比赛,发现我们写的项目耦合性高,层次不够清晰,随时相互调用,维护困难。以至于在进行版本控制,上传到Plastic SCM时,导致节点冲突而部分功能被冲突掉,最后只能返工重做。
我在暑假也了解多许多架构,发现了两个问题,一是很多框架不开源,这会直接造成部分错误无法修复或者修复困难。二是很多框架组件和中间件过于繁多,学习成本加大,比如我写音频组件更本不会用到空间音频,而很多框架直接用封装好的空间音频组件当成音频组件使用,这也会造成优化的问题。
因此我决定自己实现一套框架,这套框架最大的特点是简单易上手,并且能让项目的代码更加清晰。
概述#
主要由组成:#
- Architecture (架构)
- System (系统)
- Model (模型)
- Utility (实用工具)
- Command (命令)
- Query (查询)
- TypeEventSystem (事件系统)
- IOC (依赖注入容器)
- BindableProperty (绑定属性)
架构层级:#
• 表现层:IController 接口,负责接收输入和当状态变化时更新表现,一般情况下 MonoBehaviour 均为表现层对象。
• 系统层:ISystem 接口,帮助 IController 承担一部分逻辑,在多个表现层共享的逻辑,比如计时系统、商城系统、成就系统等。
• 模型层:IModel 接口,负责数据的定义以及数据的增删改查方法的的提供。
• 工具层:IUtility 接口,负责提供基础设施,比如存储方法、序列化方法、网络链接方法、蓝牙方法、SDK、框架集成等。
使用规则:#
• IController 更改 ISystem、IModel 的状态必须用 Command。
• ISystem、IModel 状态发生变更后通知 IController 必须用事件 或 BindableProeprty。
• IController 可以获取 ISystem、IModel 对象来进行数据查询。
• ICommand 不能有状态。
• 上层可以直接获取下层对象,下层不能获取上层对象。
• 下层像上层通信用事件。
• 上层向下层通信用方法调用,IController 的交互逻辑为特使情况,只能用 Command。
插入设计图片#
YGFramework具体设计思路#
YGFramework 的设计理念是通过依赖注入(IOC)和事件驱动的方式,实现模块间的松耦合和高内聚,从而提高代码的可维护性和扩展性。主要包括以下几个方面:
- 模块解耦:通过接口和依赖注入,使得系统、模型和实用工具彼此独立,方便后期扩展或修改。
- 事件驱动:通过事件系统,模块间可以通过发布和订阅事件的方式进行通信,避免直接的依赖关系。
- 命令与查询分离:使用命令模式执行操作,使用查询模式获取数据,遵循 CQRS(Command Query Responsibility Segregation)原则。
- 依赖倒置原则(Dependence Inversion Principle):程序要依赖于抽象接口,不要依赖于具体实现。
架构(Architecture)#
架构是框架的核心,负责系统、模型、实用工具的注册和管理,同时提供发送命令、查询和事件的功能。
IArchitecture 接口#
IArchitecture
定义了架构的基本操作,包括:
- 注册方法:注册系统(RegisterSystem)、模型(RegisterModel)和实用工具(RegisterUtility)。
- 获取方法:获取系统(GetSystem)、模型(GetModel)和实用工具(GetUtility)。
- 发送方法:发送命令(SendCommand)、查询(SendQuery)和事件(SendEvent)。
- 事件注册:注册和注销事件监听器(RegisterEvent 和 UnRegisterEvent)。
public interface IArchitecture { #region 注册(Register) /// <summary> /// 注册系统 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="system"></param> void RegisterSystem<T>(T system) where T : ISystem; /// <summary> /// 注册Model /// </summary> /// <typeparam name="TModel"></typeparam> /// <param name="model"></param> void RegisterModel<TModel>(TModel model) where TModel : IModel; /// <summary> /// 注册工具 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="utility"></param> void RegisterUtility<T>(T utility)where T:IUtility; #endregion #region 获取(Get) /// <summary> /// 获取工具 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> T GetUtility<T>() where T : class,IUtility; /// <summary> /// 获取Model /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> T GetModel <T>() where T : class,IModel; /// <summary> /// 获取system /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> T GetSystem<T>() where T : class,ISystem; #endregion #region 发送命令(SendCommand) /// <summary> /// 发送命令 /// </summary> /// <typeparam name="T"></typeparam> void SendCommand<T>()where T:ICommand,new(); /// <summary> /// 发送命令 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="command"></param> void SendCommand<T>(ICommand command) where T:ICommand; #endregion void SendEvent<T>() where T:new(); void SendEvent<T>(T e); IUnRegister RegisterEvent<T>(Action<T> onEvent); void UnRegisterEvent<T>(Action<T> onEvent); }
Architecture 抽象类 #
Architecture<T>
实现了 IArchitecture
接口,提供了架构的具体实现。它采用了单例模式,确保整个应用程序中只有一个架构实例。类中的 Init()
方法用于初始化架构,确保所有系统和模型都在使用前得到初始化。
泛型限制为无参数构造,并且实现IArchitecture接口
public abstract class Architecture<T> : IArchitecture where T : Architecture<T>, new()
用MakeSureArchitecture方法,确保Architecture初始化,用List作为缓存初始化各个层次
private List<IModel> mModels = new List<IModel> (); private List<ISystem> mSystems = new List<ISystem> (); public static Action<T> OnRegisterPatch = architecture => { }; private static T mArchitecture; public static IArchitecture Interface { get { if(mArchitecture == null) { MakeSureArchitecture(); } return mArchitecture; } } static void MakeSureArchitecture() { if (mArchitecture == null) { mArchitecture = new T(); mArchitecture.Init(); OnRegisterPatch?.Invoke(mArchitecture); foreach(var architectureModel in mArchitecture.mModels) { architectureModel.Init();//model层比system层更底层,先初始化 } mArchitecture.mModels.Clear (); foreach (var architectureSystem in mArchitecture.mSystems) { architectureSystem.Init(); } mArchitecture.mSystems.Clear(); mArchitecture.mInited = true; } }
注意在model层和system层初始化之前有一个静态委托 public static Action
还可以用OnRegisterPatch
来添加调试信息或日志记录,以便在架构注册过程中追踪架构的状态和行为。
Architecture实现IArchitecture的各个方法,里面运用到了IOC容器和自定义的事件系统
private IOCContainer mContainer = new(); protected abstract void Init(); [Obsolete("简版框架已弃用",true)] public static void Register<TInstance>(TInstance instance) { MakeSureArchitecture(); mArchitecture.mContainer.Register<TInstance>(instance); } public void RegisterModel<TModel>(TModel model) where TModel : IModel { model.SetArchitecture(this); mContainer.Register<TModel>(model); if (!mInited) { mModels.Add(model); } else { model.Init(); } } public void RegisterUtility<TUtility>(TUtility utility) where TUtility : IUtility { mContainer.Register<TUtility>(utility); } public void RegisterSystem<TSystem>(TSystem system) where TSystem : ISystem { system.SetArchitecture(this); mContainer.Register<TSystem>(system); if (!mInited) { mSystems.Add(system); } else { system.Init(); } } [Obsolete("简版框架Get已经弃用",true)] public static TInstance Get<TInstance>() where TInstance : class { MakeSureArchitecture(); return mArchitecture.mContainer.Get<TInstance>(); } public TUtility GetUtility<TUtility>() where TUtility : class, IUtility { return mContainer.Get<TUtility>(); } TModel IArchitecture.GetModel<TModel>() { return mContainer.Get<TModel>(); } TSystem IArchitecture.GetSystem<TSystem>() { return mContainer.Get<TSystem>(); } public void SendCommand<TCommand>() where TCommand : ICommand, new() { var command = new TCommand(); command.SetArchitecture(this); command.Execute(); } public void SendCommand<TCommand>(ICommand command) where TCommand : ICommand { command.SetArchitecture(this); command.Execute(); } ITypeEventSystem mTypeEventSystem = new TypeEventSystem(); public void SendEvent<TEvent>() where TEvent : new() { mTypeEventSystem.Send<TEvent>(); } public void SendEvent<TEvent>(TEvent e) { mTypeEventSystem.Send<TEvent>(e); } public IUnRegister RegisterEvent<TEvent>(Action<TEvent> onEvent) { return mTypeEventSystem.Register<TEvent>(onEvent); } public void UnRegisterEvent<TEvent>(Action<TEvent> onEvent) { mTypeEventSystem.UnRegister<TEvent>(onEvent); } }
以上代码会很容易发现我用的是接口实现这种形式注册和获取对象的方式,这种方式是符合依赖倒置原则,其核心思想是高层模块不应该依赖于低层模块,两者都应该依赖于抽象,并且抽象不应该依赖于细节,细节应该依赖于抽象。
好处如下:
• 接口设计与实现分成两个步骤,接口设计时可以专注于设计,实现时可以专注于实现。
• 接口设计时专注于设计可以减少系统设计时的干扰。
• 实现是可以替换的,比如一个接口叫 IStorage,其实现可以是 PlayerPrefsStorage、EasySaveStorage、EdtiroPrefsStorage,等切换时候只需要一行代码就可以切换了。
• 当实现细节(比如 PlayerPrefsStorage)发生变化时,其引用接口(比如 IStorage)类里的代码不会跟着改变,降低耦合。
• 等等
好处有很多,当然不好的地方就是可能要多写一点代码,但是对于大规模项目来说这点代码量来换取更健康的项目状态是很划算的。
控制器(Controller)#
控制器是系统逻辑的封装,负责与系统、模型交互并协调它们的工作。
IController 接口#
IController
继承了一系列扩展接口,这些接口赋予IController能力(与其说是赋予不如说是限制,接口的显示调用+扩展方法进行限制)
YGFramework没有将视图和控制分开,Controller就已经是顶层了,所以能直接获取system和model,当想要底层状态发生改变时发送command。
namespace FrameworkDesign { public interface IController : IBelongToArchitecture, ICanGetSystem, ICanGetModel, ICanSendCommand,ICanRegisterEvent { } } //public abstract class Abstract_yourManagerName_Controller : MonoBehaviour, IController //{ // IArchitecture IBelongToArchitecture.GetArchitecture() // { // return yourManagerName.Interface; // //实现接口IController变成继承Abstract_yourManagerName_Controller // //为了框架通用性这里不写 // } //}
IBelongToArchitecture接口(规则)#
public interface IBelongToArchitecture { IArchitecture GetArchitecture(); }
该接口在框架中的作用主要是提供一种结构化的方式,使实现该接口的类能够访问其所属的 IArchitecture
实例。这个接口的设计意图是为了实现依赖倒置原则,方便类在执行过程中获取到它们所依赖的架构服务或模块。
ICanGetSystem接口(规则)#
public interface ICanGetSystem : IBelongToArchitecture { } public static class CanGetSystemExtension { public static T GetSystem<T>(this ICanGetSystem self) where T : class, ISystem { return self.GetArchitecture().GetSystem<T>(); } }
系统(System)#
系统是架构中用于处理业务逻辑的模块,代表某一领域的操作集合。
ISystem 接口#
ISystem位于表现层之下,因此可以获取model,utilitty,和同层的system,框架规定下层通知表现层时用事件,因此也能发送事件
public interface ISystem : IBelongToArchitecture,ICanSetArchitecture,ICanGetModel,ICanGetUtility,ICanRegisterEvent,ICanSendCommand,ICanGetSystem,ICanSendEvent { void Init(); }
ICanSetArchitecture接口(规则)#
这里发现多了一个ICanSetArchitecture接口约束实际上之前void SetArchitecture(IArchitecture architecture);是写在IBelongToArchitecture里的,但发现初始化会造成递归调用,使其栈溢出故而分开。
public interface ICanSetArchitecture { void SetArchitecture(IArchitecture architecture); }
AbstractSystem 抽象类#
AbstractSystem
提供了 ISystem
的基本实现,开发者可以通过继承该类来实现具体的系统,并在 OnInit()
方法中定义初始化逻辑。
public abstract class AbstractSystem : ISystem { private IArchitecture mArchitecture = null; IArchitecture IBelongToArchitecture.GetArchitecture() { return mArchitecture; } void ICanSetArchitecture.SetArchitecture(IArchitecture architecture) { mArchitecture = architecture; } void ISystem.Init() { OnInit(); } protected abstract void OnInit(); }
模型(Model)#
模型负责管理数据和状态,并提供对数据的操作接口。
IModel 接口#
IModel 定义了模型的基本行为和扩展接口,主要用于获取实用工具和发送事件。
public interface IModel : IBelongToArchitecture,ICanSetArchitecture,ICanGetUtility,ICanSendEvent { void Init(); }
AbstractModel抽象类#
AbstractModel是模型的抽象实现,开发者可以通过继承该类来实现具体的模型逻辑,并在 OnInit()
方法中定义初始化逻辑。
同样的这样设计符合依赖倒置原则
public abstract class AbstractModel : IModel { private IArchitecture mArchitecture = null; IArchitecture IBelongToArchitecture.GetArchitecture() { return mArchitecture; } void ICanSetArchitecture.SetArchitecture(IArchitecture architecture) { mArchitecture = architecture; } void IModel.Init() { OnInit(); } protected abstract void OnInit(); }
实用工具(Utility)#
实用工具用于封装常用的、与具体业务无关的功能,例如日志、文件操作等。
IUtility 接口#
IUtility 是一个标记接口,表示某个类是一个实用工具,可以在架构中注册和获取。因为与业务无关,所以不用获取任何东西。
public interface IUtility { }
命令(Command)#
命令模式用于封装对系统或模型的操作,允许通过 SendCommand
方法来执行某个操作。
ICommand接口#
ICommand定义了命令的行为,包含 Execute()
方法,表示命令的执行逻辑。
public interface ICommand:IBelongToArchitecture,ICanSetArchitecture,ICanGetSystem,ICanGetModel,ICanGetUtility,ICanSendEvent,ICanSendCommand { void Execute(); }
AbstractCommand抽象类#
AbstractCommand
提供了命令的基本实现,开发者可以通过继承该类来实现具体的命令,并在 OnExecute()
方法中定义执行逻辑。
public abstract class AbstractCommand : ICommand { private IArchitecture mArchitecture; void ICommand.Execute() { OnExecute(); } IArchitecture IBelongToArchitecture.GetArchitecture() { return mArchitecture; } void ICanSetArchitecture.SetArchitecture(IArchitecture architecture) { mArchitecture = architecture; } protected abstract void OnExecute(); }
查询(Query)#
查询模式用于获取数据,用于实现 CQRS 模式中的查询操作。
IQuery接口 #
IQueryDo()
方法,返回查询结果。
public interface IQuery<TResult> : IBelongToArchitecture, ICanSetArchitecture, ICanGetModel, ICanGetSystem, ICanSendQuery { TResult Do(); }
AbstractQuery抽象类 #
AbstractQuery<T>
提供了查询的基本实现,开发者可以通过继承该类来实现具体的查询逻辑,并在 OnDo()
方法中定义查询逻辑。
public abstract class AbstractQuery<T> : IQuery<T> { public T Do() { return OnDo(); } protected abstract T OnDo(); private IArchitecture mArchitecture; public IArchitecture GetArchitecture() { return mArchitecture; } public void SetArchitecture(IArchitecture architecture) { mArchitecture = architecture; } }
事件系统(Event System)#
事件系统用于模块间的解耦通信,允许通过注册事件监听器和发送事件来实现模块间的消息传递。
旧版事件类
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; [Obsolete("在短学期中发现Event脚本功能不够完善,请换成TypeEventSystem",true)] public class Event<T> where T : Event<T> { //这是一个泛型约束,限制了T必须继承或实现Event<T>。换句话说,任何作为T的类型参数必须是Event<T> 的子类(或Event<T> 本身) private static Action mOnEventTrigger; public static void Register(Action OnEvent) { mOnEventTrigger += OnEvent; } public static void UnRegister(Action OnEvent) { mOnEventTrigger -= OnEvent; } public static void Trigger() { mOnEventTrigger?.Invoke(); } }
这个简单的事件系统,提供了注册、注销、触发事件的基本功能。它只能处理无参事件的触发。事件的类型 T
被约束为继承自 Event<T>
,这可能会限制事件的多样性与扩展性。并且没有内置的事件生命周期管理机制,需要手动管理事件的注册与注销。当需要处理更复杂的事件时,可能会显得力不从心。
重构后的事件系统允许注册和触发带参数的事件。通过泛型可以支持多种类型的事件处理,增强了代码的可扩展性和适应性。支持通过接口 ITypeEventSystem
和 IUnRegister
进行事件注册和注销的管理,提供了更丰富的事件管理功能。使用 Dictionary<Type, IRegistrations>
来管理不同类型的事件,可以轻松处理多种类型的事件。提供了更复杂的事件注册与注销机制,包括在对象销毁时自动注销事件,避免内存泄漏。提供了 IUnRegister
接口,使得事件的注销更加灵活,可以按需控制事件生命周期。提供了扩展方法 UnRegisterWhenGameObjectDestroyed
,可以在 GameObject 销毁时自动注销事件,增强了系统的健壮性。UnRegisterOnDestoryTrigger
通过 HashSet<IUnRegister>
管理事件注销,确保不会因为对象销毁而导致内存泄漏或错误的事件调用。适用于更复杂、更通用的事件管理场景。通过泛型支持各种类型的事件,适合大型项目或需要灵活事件处理的系统。
ITypeEventSystem 接口#
ITypeEventSystem
定义了事件系统的行为,支持注册事件监听器、发送事件和注销事件监听器。
public interface ITypeEventSystem { /// <summary> /// 发送事件 /// </summary> /// <typeparam name="T"></typeparam> void Send<T>() where T : new(); void Send<T>(T e); /// <summary> /// 注册事件 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="onEvent"></param> /// <returns></returns> IUnRegister Register<T>(Action<T> onEvent); /// <summary> /// 销毁事件 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="onEvent"></param> void UnRegister<T>(Action<T> onEvent); } /// <summary> /// 注销的接口 /// </summary> public interface IUnRegister { void UnRegister(); }
TypeEventSystem 类#
TypeEventSystem
是事件系统的具体实现,使用内部的注册表来管理事件和监听器的映射关系。
public class TypeEventSystem : ITypeEventSystem { interface IRegistrations { } class Registrations<T> : IRegistrations { public Action<T> OnEvent = obj => { }; } private Dictionary<Type, IRegistrations> mEventRegistrations = new Dictionary<Type, IRegistrations>(); public void Send<T>() where T : new() { var e = new T(); Send<T>(e); } public void Send<T>(T e) { var type = typeof(T); IRegistrations eventRegistrations; if (mEventRegistrations.TryGetValue(type, out eventRegistrations)) { (eventRegistrations as Registrations<T>)?.OnEvent.Invoke(e); } } public IUnRegister Register<T>(Action<T> onEvent) { var type = typeof(T); IRegistrations eventRegistrations; if (mEventRegistrations.TryGetValue(type, out eventRegistrations)) { } else { eventRegistrations = new Registrations<T>(); mEventRegistrations.Add(type, eventRegistrations); } (eventRegistrations as Registrations<T>).OnEvent += onEvent; return new TypeEventSystemUnRegister<T>() { OnEvent = onEvent, TypeEventSystem = this }; } public void UnRegister<T>(Action<T> onEvent) { var type = typeof(T); IRegistrations eventRegistrations; if (mEventRegistrations.TryGetValue(type, out eventRegistrations)) { (eventRegistrations as Registrations<T>).OnEvent -= onEvent; } } }
IOC 容器#
IOC 容器用于管理对象的创建和生命周期,通过依赖注入的方式提供实例。这个 IOCContainer
提供了基础的依赖注入功能,可以帮助开发者更好地管理对象的创建和依赖。虽然它非常简单,但在小型项目中已经足够实用。但我觉得对于我如今的项目已经充够了.
IOCContainer 类#
IOCContainer
实现了对象的注册和获取功能,通过字典管理类型与实例的映射关系,支持依赖注入。
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; namespace FrameworkDesign { public class IOCContainer { private Dictionary<Type,object> mInstances = new Dictionary<Type,object>(); public void Register<T>(T instance) { var key = typeof(T); if (mInstances.ContainsKey(key)) { mInstances[key] = instance; } else { mInstances.Add(key, instance); } } public T Get<T>() where T : class { var key =typeof(T); if (mInstances.TryGetValue(key, out object retInstance)) { return retInstance as T; } return null; } } }
可绑定属性(Bindable Property)#
可绑定属性是一种特殊的数据容器,用于将数据变化与 UI 或其他模块的更新逻辑绑定在一起。
BindableProperty`类 #
BindableProperty<T>
包装了一个值,并提供了 Register
和 RegisterWithInitValue
方法,用于注册数据变化监听器。当属性值发生变化时,监听器将被触发,执行相应的更新逻辑。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace FrameworkDesign { public class BindableProperty<T>where T : IEquatable<T> { //当你希望比较两个对象是否相等时,通常会使用Equals方法。IEquatable<T>接口允许你为自定义类型提供特定的相等性逻辑,确保在比较两个对象时考虑到特定的属性或字段。 //例如,int、string、DateTime等类型都实现了IEquatable<T> 接口,这样可以确保你在使用泛型类时,能够通过T的Equals方法来比较实例。 private T mValue = default(T); public T Value { get { return mValue; } set { if(!value.Equals(mValue)) { mValue = value; mOnValueChanged?.Invoke(mValue); } } } public Action<T> mOnValueChanged = (v) => { }; public IUnRegister RegisterOnValueChanged(Action<T> onValueChanged) { mOnValueChanged += onValueChanged; return new BindablePropertyUnRegister<T>() { BindableProperty = this, OnValueChanged = onValueChanged }; } public void UnRegisterOnValueChanged(Action<T> onValueChanged) { mOnValueChanged -= onValueChanged; } public class BindablePropertyUnRegister<T1> : IUnRegister where T1 : IEquatable<T1> { public BindableProperty<T1> BindableProperty { get; set; } public Action<T1> OnValueChanged { get; set; } public void UnRegister() { BindableProperty.UnRegisterOnValueChanged(OnValueChanged); BindableProperty = null; OnValueChanged = null; } } } }
使用示例#
初始化架构#
开发者需要定义一个继承自 Architecture<T>
的具体架构类,并实现 Init()
方法,在该方法中注册系统、模型和实用工具。
public class YourProjectArchitecture : Architecture<YourProjectArchitecture> { protected override void Init() { RegisterSystem(new YourSystem()); RegisterModel(new YourModel()); RegisterUtility(new YourUtility()); } }
定义系统#
public class YourSystem : AbstractSystem { protected override void OnInit() { // 初始化系统逻辑 } }
定义模型#
public class YourModel : AbstractModel { protected override void OnInit() { // 初始化模型逻辑 } }
发送命令#
public class StartCommand : AbstractCommand { protected override void OnExecute() { var gameSystem = GetSystem<GameSystem>(); gameSystem.StartGame(); } }
查询数据#
public class GetScoreQuery : AbstractQuery<int> { protected override int OnDo() { var gameModel = GetModel<GameModel>(); return gameModel.Score; } }
使用事件系统#
// 发送事件 SendEvent(new GameOverEvent()); // 注册事件监听器 RegisterEvent<GameOverEvent>(OnGameOver); // 事件处理 private void OnGameOver(GameOverEvent e) { Debug.Log("Game Over"); }
使用可绑定属性#
public class YourModel : AbstractModel { public BindableProperty<int> Score { get; private set; } = new BindableProperty<int>(0); protected override void OnInit() { // 绑定属性变化 Score.Register(OnScoreChanged); } private void OnScoreChanged(int newScore) { Debug.Log("Score updated: " + newScore); } }
IStorage工具层MySql方法封装#
同样按照依赖倒置原则进行封装
public interface IStorageInMySql:IUtility { } public class Database:IStorageInMySql { private static string connstr = "server=127.0.0.1;database=YGFramework;user=root;password=chenyigen;charset=utf8"; public static MySqlDataReader ExecuteReader(string sqlString) { MySqlConnection connection = new MySqlConnection(connstr); MySqlCommand cmd = new MySqlCommand(sqlString, connection); MySqlDataReader? myReader = null; try { connection.Open(); myReader = cmd.ExecuteReader(CommandBehavior.CloseConnection); return myReader; } catch (MySqlException e) // 使用 MySqlException 而不是 SqlException { connection.Close(); throw new Exception(e.Message); } finally { if (myReader == null) { cmd.Dispose(); connection.Close(); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!