蛙蛙推荐:利用操作符重载提高程序的可读性
摘要:以前做过一个简单的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也可以自由控制。
相关链接