SHOJI's Code
 仕事や趣味で書いた各種言語のプログラミングコード(エクセルVBA,PHP,C/C++/C#,JavaScript等)、その他雑記。
2017.08<<123456789101112131415161718192021222324252627282930>>2017.10
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

前回の記事で取り上げた「型によらない演算」をもうちょっと進めてみることにした。

まず、以下のようなコードはコンパイルエラーになる
class Test<T> {
T _value1;
T _value2;

public Test(T v1, T v2) {
_value1 = v1;
_value2 = v2;
}

public T Sum() {
return _value1 + _value2;// 演算子 '+' を 'T' と 'T' 型のオペランドに適用することはできません
}
}


このようにしたいことはよくある。しかし、単純にはいかない。非常にもどかしい・・・。

そこで、今回、とある構造体を作成してみた。
それを使い以下のように書くとエラーは起こらなくなり、実際に演算も行われる。
class Test<T> {
Arithmetician<T> _value1;
Arithmetician<T> _value2;

public Test(T v1, T v2) {
_value1 = v1;
_value2 = v2;
}

public T Sum() {
return _value1 + _value2;
}
}



「Arithmetician」というジェネリック型がそれである。
仕組みは、演算子をオーバーロードし、その中で式ツリー(Expression)を作成し、実行している。

ソースは追記に載せている。ただし毎度のことだが、適当に作っていて大したテストもしてないので、利用されるときはご注意を。
public struct Arithmetician<T>
{
private T _value;

public static readonly Arithmetician<T> ZeroOrNull = new Arithmetician<T>(default(T));

public Arithmetician(T value)
{
_value = value;
}

public T Value {get {return _value;} set {_value = value;}}

public Type Type {get {return typeof(T);}}

public static Arithmetician<T> Convert(object arg)
{
try {
return new Arithmetician<T>(convert<object,T>(arg));
}
catch {
string msg = "'{0}' から '{1}' へ変換を行うことができません";
throw new InvalidCastException(string.Format(msg, typeof(T).Name, arg.GetType().Name));
}
}

public override string ToString()
{
return this.Value.ToString();
}

public override bool Equals(object obj)
{
return this.Value.Equals(obj);
}

public override int GetHashCode()
{
return this.Value.GetHashCode();
}

public static implicit operator Arithmetician<T>(T arg)
{
return new Arithmetician<T>(arg);
}

public static implicit operator T(Arithmetician<T> arg)
{
return arg.Value;
}

public static Arithmetician<T> operator +(Arithmetician<T> arg1, object arg2)
{
try {
return new Arithmetician<T>(doBinaryOperation("+", Expression.Add, arg1.Value, arg2));
}
catch(Exception ex) {
try {
return new Arithmetician<T>(concat(arg1.Value, arg2));
}
catch {
throw ex;
}
}
}

public static Arithmetician<T> operator -(Arithmetician<T> arg1, object arg2)
{
return new Arithmetician<T>(doBinaryOperation("-", Expression.Subtract, arg1.Value, arg2));
}

public static Arithmetician<T> operator *(Arithmetician<T> arg1, object arg2)
{
return new Arithmetician<T>(doBinaryOperation("*", Expression.Multiply, arg1.Value, arg2));
}

public static Arithmetician<T> operator /(Arithmetician<T> arg1, object arg2)
{
return new Arithmetician<T>(doBinaryOperation("/", Expression.Divide, arg1.Value, arg2));
}

public static Arithmetician<T> operator %(Arithmetician<T> arg1, object arg2)
{
return new Arithmetician<T>(doBinaryOperation("%", Expression.Modulo, arg1.Value, arg2));
}

public static Arithmetician<T> operator ++(Arithmetician<T> arg)
{
return arg + 1;
}

public static Arithmetician<T> operator --(Arithmetician<T> arg)
{
return arg - 1;
}

public static Arithmetician<T> operator &(Arithmetician<T> arg1, Arithmetician<T> arg2)
{
return new Arithmetician<T>(doBinaryOperation("&", Expression.And, arg1.Value, arg2.Value));
}

public static Arithmetician<T> operator |(Arithmetician<T> arg1, Arithmetician<T> arg2)
{
return new Arithmetician<T>(doBinaryOperation("|", Expression.Or, arg1.Value, arg2.Value));
}

public static Arithmetician<T> operator ^(Arithmetician<T> arg1, Arithmetician<T> arg2)
{
return new Arithmetician<T>(doBinaryOperation("^", Expression.ExclusiveOr, arg1.Value, arg2.Value));
}

public static Arithmetician<T> operator ~(Arithmetician<T> arg)
{
return new Arithmetician<T>(doUnaryOperation("~", Expression.Not, arg.Value));
}

public static Arithmetician<T> operator <<(Arithmetician<T> arg1, int arg2)
{
return new Arithmetician<T>(doBinaryOperation("<<", Expression.LeftShift, arg1.Value, arg2));
}

public static Arithmetician<T> operator >>(Arithmetician<T> arg1, int arg2)
{
return new Arithmetician<T>(doBinaryOperation(">>", Expression.RightShift, arg1.Value, arg2));
}

public static bool operator ==(Arithmetician<T> arg1, Arithmetician<T> arg2)
{
return doRelationalOperation("==", Expression.Equal, arg1, arg2);
}

public static bool operator !=(Arithmetician<T> arg1, Arithmetician<T> arg2)
{
return doRelationalOperation("!=", Expression.NotEqual, arg1, arg2);
}

public static bool operator >(Arithmetician<T> arg1, Arithmetician<T> arg2)
{
return doRelationalOperation(">", Expression.GreaterThan, arg1, arg2);
}

public static bool operator >=(Arithmetician<T> arg1, Arithmetician<T> arg2)
{
return doRelationalOperation(">=", Expression.GreaterThanOrEqual, arg1, arg2);
}

public static bool operator <(Arithmetician<T> arg1, Arithmetician<T> arg2)
{
return doRelationalOperation("<", Expression.LessThan, arg1, arg2);
}

public static bool operator <=(Arithmetician<T> arg1, Arithmetician<T> arg2)
{
return doRelationalOperation("<=", Expression.LessThanOrEqual, arg1, arg2);
}

private static TDest convert<TSrc,TDest>(TSrc value)
{
ParameterExpression x = Expression.Parameter(typeof(TSrc), "x");
Func<TSrc,TDest> fn = Expression.Lambda<Func<TSrc,TDest>>(Expression.Convert(x, typeof(TDest)), x).Compile();
return fn(value);
}

private static T doUnaryOperation(string name, Func<ParameterExpression,UnaryExpression>op, T arg)
{
ParameterExpression x = Expression.Parameter(typeof(T), "x");

Expression<Func<T,T>> expr;
try {
expr = Expression.Lambda<Func<T,T>>(op(x), x);
}
catch {
string msg = "'{0}' に対する演算子 '{1}' が見つかりません";
throw new InvalidOperationException(string.Format(msg, typeof(T).Name, name));
}

try {
Func<T,T> func = expr.Compile();
return func(arg);
}
catch(DivideByZeroException) {
throw;
}
catch(OverflowException) {
throw;
}
catch(NotFiniteNumberException) {
throw;
}
catch(Exception ex) {
string msg = "'{0}' に対して 演算 '{1}' を行った際にエラーが発生しました";
throw new ArithmeticException(string.Format(msg, typeof(T).Name, name), ex);
}
}

private static T doBinaryOperation(string name, Func<ParameterExpression,ParameterExpression,BinaryExpression>op, T arg1, object arg2)
{
try {
arg2 = getArgumentValue(arg2);
}
catch {
string msg = "'{0}' 型から値を取得できません";
throw new ArgumentException(string.Format(msg, arg2.GetType()));
}

ParameterExpression x = Expression.Parameter(typeof(T), "x");
ParameterExpression y = Expression.Parameter(arg2.GetType(), "y");

LambdaExpression expr;
try {
expr = Expression.Lambda(op(x,y), x, y);
}
catch {
string msg = "'{0}' と '{1}' 型に対する演算子 '{2}' が見つかりません";
throw new InvalidOperationException(string.Format(msg, typeof(T).Name, arg2.GetType().Name, name));
}

try {
Delegate func = expr.Compile();
object value = func.DynamicInvoke(arg1, arg2);
return (T)value;
}
catch(DivideByZeroException) {
throw;
}
catch(OverflowException) {
throw;
}
catch(NotFiniteNumberException) {
throw;
}
catch(Exception ex) {
string msg = "'{0}' と '{1}' 型に対して演算('{2}')を行った際にエラーが発生しました";
throw new ArithmeticException(string.Format(msg, typeof(T).Name, arg2.GetType().Name, name), ex);
}
}

private static bool doRelationalOperation(string name, Func<ParameterExpression,ParameterExpression,BinaryExpression>op, T arg1, T arg2)
{
ParameterExpression x = Expression.Parameter(typeof(T), "x");
ParameterExpression y = Expression.Parameter(typeof(T), "y");

Expression<Func<T,T,bool>> expr;
try {
expr = Expression.Lambda<Func<T,T,bool>>(op(x,y), x, y);
}
catch {
string msg = "'{0}' 型同士を比較する演算子 '{1}' が見つかりません";
throw new InvalidOperationException(string.Format(msg, typeof(T).Name, name));
}

try {
Func<T,T,bool> func = expr.Compile();
return func(arg1, arg2);
}
catch(DivideByZeroException) {
throw;
}
catch(OverflowException) {
throw;
}
catch(NotFiniteNumberException) {
throw;
}
catch(Exception ex) {
string msg = "'{0}' 型同士の比較('{1}')を行った際にエラーが発生しました";
throw new ArithmeticException(string.Format(msg, typeof(T).Name, name), ex);
}
}

private static T concat(T arg1, object arg2)
{
try {
arg2 = getArgumentValue(arg2);
}
catch {
string msg = "'{0}' 型から値を取得できません";
throw new ArgumentException(string.Format(msg, arg2.GetType()));
}

ParameterExpression x = Expression.Parameter(typeof(object), "x");
ParameterExpression y = Expression.Parameter(typeof(object), "y");

MethodInfo mi = typeof(T).GetMethod("Concat", new Type[]{typeof(object),typeof(object)});
LambdaExpression expr;
try {
expr = Expression.Lambda(Expression.Call(mi, x, y), x, y);
}
catch {
string msg = "'{0}' 型に '{1}' 型を連結するためのメソッド 'Concat' が見つかりません";
throw new InvalidOperationException(string.Format(msg, typeof(T).Name, arg2.GetType().Name));
}

try {
Delegate func = expr.Compile();
object value = func.DynamicInvoke(arg1, arg2);
return (T)value;
}
catch(DivideByZeroException) {
throw;
}
catch(OverflowException) {
throw;
}
catch(NotFiniteNumberException) {
throw;
}
catch(Exception ex) {
string msg = "メソッド {0}.Concat('{1}', '{2}') 実行中にエラーが発生しました";
throw new ArithmeticException(string.Format(msg, typeof(T).Name, arg2.GetType().Name), ex);
}
}

private static object getArgumentValue(object value)
{
if (value is Arithmetician<T>) return ((Arithmetician<T>)value).Value;

if (value.GetType()==typeof(Arithmetician<>)) {
PropertyInfo pi = value.GetType().GetProperty("Value");
return pi.GetGetMethod().Invoke(value, new object[]{});
}

return value;
}
}


テーマ:プログラミング - ジャンル:コンピュータ
コメント
この記事へのコメント
コメントを投稿する

管理者にだけ表示を許可する
トラックバック
この記事のトラックバックURL
この記事へのトラックバック
copyright © 2004-2006 SHOJI, Powered By FC2ブログ all rights reserved.
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。