设为首页 加入收藏

TOP

C# 中利用运行时编译实现泛函(一)
2015-07-16 12:56:08 来源: 作者: 【 】 浏览:11
Tags:利用 行时 编译 实现

引言


我想要分享一个新模式,我开发来用于在 C# 中利用运行时编译进行泛型计算。


过去的几年里我已经在编程并且看到许多在 C# 中实现泛型数学的例子,但是没有一个能做得非常地好。在第一部分中我将一步步地解说我看到的一些例子,同时也会说明为什么他们没有向泛函提供好的模式。


我开发了这个模式以作为我的 Seven Framework 框架工程的一部分。如果你感兴趣的话你可以点击:https://github.com/53V3N1X/SevenFramework


问题概述


首先,根本的问题是在 C# 中处理泛型就像基类一样。除了 System.Object 基类,他们都没有隐式成员。也就是说,相对于标准数值类型(int,double,decimal,等)他们没有算数运算符和隐式转换。EXAMPLE 1 是一种理想情况,但是这段代码在标准 C# 中是不能编译的。


// EXAMPLE 1 ----------------------------------------
namespace ConsoleApplication
{? public class Program?
? ? {? public static void Main(string[] args)? ?
? ? ? {? Sum(new int[] { 1, 2, 3, 4, 5 });? ? }? ?
? ? public static T Sum(T[] array)? ?
? ? {? T sum = 0; // (1) cannot convert int to generic? ? ?
? ? ? ? for (int i = 0; i < array.Length; i++)? ? ? ?
? ? ? ? sum += array[i]; // (2) cannot assume addition operator on generic? ? ?
? ? ? ? return sum;? ? }?
? ? ? ? ? ? ? }
}


确实如此,EXAMPLE 1 不能通过编译因为:


现在我们了解了根本问题后,让我们开始寻找一些方法克服它。


接口化解决方法


C#中的 where 子句是一种强迫泛型满足某种类型的约束。然而,用这种方法就要求有一种不存在于C#中的基本数值类型。C#有这样一种基本数值类型是最接近强制泛型成为数值类型的可能了,但这并不能在数学上帮助我们。EXAMPLE 2 仍然不能够通过编译,但如果我们创造出了我们自己的基本数值类型,这将成为可能。


Hide Copy Code


// EXAMPLE 2 ----------------------------------------
namespace ConsoleApplication {?
public class Program?
{? ?
public static void Main(string[] args)? ?
{? ? ?
Sum(new int[] { 1, 2, 3, 4, 5 });? ?
}? ?
public static T Sum(T[] array)? ? ?
where T : number? // (1) there is no base "number" type in C#? ?
{? ? ? T sum = 0;? ? ?
for (int i = 0; i < array.Length; i++)? ? ? ?
sum += array[i];? ? ?
return sum;? ? }?
}
}


现在 EXAMPLE 2 还不能编译因为:


如果我们实现了我们自己的基本“数值”类型,就可以让它通过编译。我们所必需做的就是迫使这个数值类型拥有C#基本数值类型一般的算数运算符和隐式转换。逻辑上来讲,这应该是一个接口。


然而,即使我们自己做数值接口,我们仍然有一个重大问题。我们将不能够对 C# 中的基本类型做通用数学计算,因为我们不能改变 int,double,decimal 等的源代码来实现我们的接口。所以,我们不仅必须编写自己的基本接口,还需要为C#中的原始类型编写包装器。
在例3中,我们有我们自己的数值接口,“数字”,和原始类型int的包装器,Integer32。


// EXAMPLE 3 ----------------------------------------
namespace ConsoleApplication
{?
public class Program?
{? ?
public static void Main(string[] args)? ?
{? ? ?
Sum(new Number[]? ? ?
{? ? ? ?
new Integer32(1), // (1) initialization nightmares...? ? ? ?
new Integer32(2),? ? ? ? ?
new Integer32(3),? ? ? ?
new Integer32(4),? ? ? ?
new Integer32(5)? ? ?
});? ?
?}? ?
public static Number Sum(Number[] array)? ?
{? ? ?
Number sum = array[0].GetZero(); // (2) instance-based factory methods are terrible design? ? ?
for (int i = 0; i < array.Length; i++)? ? ? ?
sum = sum.Add(array[i]);? ? ?
return sum;? ?
}?
?}?
public interface Number?
{? ?
Number GetZero(); // (2) again... instance based factory methods are awful? ?
Number Add(Number other);?
}?
public struct Integer32 : Number // (3) C# primitives cannot implement "Number"?
{? ?
int _value;? ?
public Integer32(int value)? ?
{? ? ?
this._value = value;? ?
}? ?
Number Number.GetZero()? ?
{? ? ?
return new Integer32(0);? ?
}? ? // (4) you will have to re-write these functions for every single type? ? ?
Number Number.Add(Number other)? ?
{? ? ?
return new Integer32(_value + ((Integer32)other)._value);? ?
}?
}
} // (5) this code is incredibly slow


好的,这样 EXAMPLE 3 就编译了,但是它有点糟,为什么呢:


如果你的泛函库不能使在 C# 中完成泛型数学运算,没有人会对此买单。因此,接下里让我们处理这个问题。如果不能够修改 C# 原始数据类型去实现想要的接口,那么我们就创造另一种类型能够处理那些类型具有的所有数学运算。这就是在 .Net 框架中广泛使用的标准提供者模式。


边注/发泄:就我个人来说,我憎恨提供者模式。但我至今没有发现使用委托有处理不好的例子。当大量创建大量提供者时,他们没有使用委托。

当我们使用提供者模式,本质上仍是做和以前同样的事,但一个提供者类就能处理所有的数学运算。在EXAMPLE 4中检验它:


/EXAMPLE 4 ----------------------------------------
namespace ConsoleApplication
{?
public class Program?
{? ?
public static void Main(string[] args)? ?
{? ? ?
Sum(new int[] { 1, 2, 3, 4, 5}, new MathProvider_int());? ?
}? ? // (1) all the methods need access to the

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇使用 FFmpeg 处理高质量 GIF 图片 下一篇深入理解JavaScript new的机制

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: