蛙蛙推荐:利用操作符重载提高程序的可读性

摘要:以前做过一个简单的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也可以自由控制。

 

相关链接

Operator Overloading Tutorial

蛙蛙推荐:简化基于数据库的DotNet应用程序开发

posted @ 2010-08-09 22:35  蛙蛙王子  Views(2146)  Comments(13Edit  收藏  举报