蛙蛙推荐:利用操作符重载提高程序的可读性
摘要:以前做过一个简单的ORMapping的小组件,但c#代码转换成SQL代码的部分不是很直观,用到了很多丑陋的方法名来代替操作符,今天受脑袋指点,其实可以用操作符重载来实现c#直接写数据查询条件。
c#可以重载很多操作符,对于SQL操作来说,主要是 ==,!=,&&,||(为了演示简单,暂不实现!,<,>,<=,>=等),其中前两个重载的话,还得必须重载true和false操作符,后两个不能直接重载,但可以重载&和|来代替。
我们先定义一个表示数据库查询条件的类,如下

public class DbCondition {
public readonly string _fieldName = string.Empty;
public string _sql = string.Empty;
public DbCondition(string fieldName) {
_fieldName = fieldName;
}
}
有一些SQL的语法元素,如like,between,in在c#里没有对应的操作符,所以我们只能以方法的形式来实现它们了。

private DbCondition() { }
public DbCondition Like(string like) {
DbCondition result = new DbCondition();
result._sql = string.Format("{0} like '{1}'", _fieldName, like);
return result;
}
public DbCondition Between(DateTime dt1, DateTime dt2) {
DbCondition result = new DbCondition();
result._sql = string.Format("{0} between '{1}' and '{2}'", _fieldName, dt1, dt2);
return result;
}
public DbCondition In(IEnumerable<int> arr) {
DbCondition result = new DbCondition();
StringBuilder sb = new StringBuilder();
foreach (int item in arr) {
sb.AppendFormat("{0},", item);
}
sb.Remove(sb.Length - 1, 1);
result._sql = string.Format("{0} in ({1})", _fieldName, sb);
return result;
}
public DbCondition In(IEnumerable<string> arr) {
DbCondition result = new DbCondition();
StringBuilder sb = new StringBuilder();
foreach (string item in arr) {
sb.AppendFormat("'{0}',", item);
}
sb.Remove(sb.Length - 1, 1);
result._sql = string.Format("{0} in ({1})", _fieldName, sb);
return result;
}
现在轮到那些等于,不等于,与和或的实现了,其中逻辑运算符&和|的参数是能是两个同类型的参数,而比较操作符的第二个参数就可以是任意类型了,比如常见的int,bool,string,每个操作符重载的实现也比较简单,主要是操作_sql字段,拼写成SQL。

public static DbCondition operator &(DbCondition x, DbCondition y) {
DbCondition result = new DbCondition();
result._sql = string.Format(" {0} and {1} ", x._sql, y._sql);
return result;
}
public static DbCondition operator |(DbCondition x, DbCondition y) {
DbCondition result = new DbCondition();
result._sql = string.Format(" {0} or {1} ", x._sql, y._sql);
return result;
}
public static DbCondition operator ==(DbCondition x, int y) {
DbCondition result = new DbCondition();
result._sql = string.Format("{0} = {1}", x._fieldName, y);
return result;
}
public static DbCondition operator !=(DbCondition x, int y) {
DbCondition result = new DbCondition();
result._sql = string.Format("{0} <> {1}", x._fieldName, y);
return result;
}
public static DbCondition operator ==(DbCondition x, string y) {
DbCondition result = new DbCondition();
result._sql = string.Format("{0} = '{1}'", x._fieldName, y);
return result;
}
public static DbCondition operator !=(DbCondition x, string y) {
DbCondition result = new DbCondition();
result._sql = string.Format("{0} <> '{1}'", x._fieldName, y);
return result;
}
public static DbCondition operator ==(DbCondition x, bool y) {
DbCondition result = new DbCondition();
result._sql = string.Format("{0} = {1}", x._fieldName, y ? 1 : 0);
return result;
}
public static DbCondition operator !=(DbCondition x, bool y) {
DbCondition result = new DbCondition();
result._sql = string.Format("{0} <> {1}", x._fieldName, y ? 1 : 0);
return result;
}
因为我们重载了比较操作符和逻辑操作符,而一个bool表达式有短路的问题,比如一个或关系连接的两个bool表达式,如果左边的表达式返回false,右边的表达式就不执行了,所以我们还得重载true和false两个操作符,让他都返回false,如下
public static bool operator false(DbCondition x) { return false; }
public static bool operator true(DbCondition x) { return false; }
OK,剩下的都是一些常规的重载了,都是统一的套路

public override bool Equals(object obj) {
DbCondition cond = obj as DbCondition;
if (cond == null) return false;
if (object.ReferenceEquals(this, cond) == true) return true;
if (cond._fieldName != this._fieldName) return false;
if (cond._sql != this._sql) return false;
return true;
}
public override int GetHashCode() {
int hashcode = 0;
if (!string.IsNullOrEmpty(_fieldName)) hashcode ^= _fieldName.GetHashCode();
if (!string.IsNullOrEmpty(_sql)) hashcode ^= _sql.GetHashCode() << 1;
return hashcode;
}
public override string ToString() {
return _sql;
}
下面开始准备测试了,先准备一个实体类,该实体类包含一些静态的条件,分别对应数据库表的列,真实情况下,这些实体类是由代码生成器生成的。

public class User {
public static DbCondition CdtUserId { get { return new DbCondition("UserId"); } }
public static DbCondition CdtName { get { return new DbCondition("Name"); } }
public static DbCondition CdtNickName { get { return new DbCondition("NickName"); } }
public static DbCondition CdtSex { get { return new DbCondition("Sex"); } }
public static DbCondition CdtRegistTime { get { return new DbCondition("RegistTime"); } }
}
写一些测试代码,看下效果

Console.WriteLine(User.CdtName == "蛙蛙");
Console.WriteLine(User.CdtName == "蛙蛙" || User.CdtNickName == "蛙蛙");
Console.WriteLine(User.CdtName == "蛙蛙" && User.CdtSex == true);
Console.WriteLine(User.CdtName.Like("蛙蛙%")
&& User.CdtRegistTime.Between(DateTime.Now.AddMonths(-1), DateTime.Now));
Console.WriteLine(User.CdtNickName != "蛙蛙"
&& User.CdtUserId.In(Enumerable.Range(100,10)));
测试输出如下

Name = '蛙蛙'
Name = '蛙蛙' or NickName = '蛙蛙'
Name = '蛙蛙' and Sex = 1
Name like '蛙蛙%' and RegistTime between '2010-7-9 22:27:11' and '2010-8-9 22:2
7:11'
NickName <> '蛙蛙' and UserId in (100,101,102,103,104,105,106,107,108,109)
这样写出来的对象查询语句和LINQ格式差不多了,很直观,而且生成的SQL也可以自由控制。
相关链接
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构