Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/296.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/gwt/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
用C#代替OO的显式方法表-好吗?糟糕?_C#_Generics_Oop_Functional Programming_Polymorphism - Fatal编程技术网

用C#代替OO的显式方法表-好吗?糟糕?

用C#代替OO的显式方法表-好吗?糟糕?,c#,generics,oop,functional-programming,polymorphism,C#,Generics,Oop,Functional Programming,Polymorphism,我希望标题听起来不要太主观;我绝对不是要就OO展开一般性的辩论。我只想讨论解决以下问题的不同方法的基本利弊 让我们举一个最简单的例子:您想用函数来表示抽象数据类型T,这些函数可以将T作为输入、输出或两者兼而有之: f1:取一个T,返回一个int f2:获取一个字符串,返回一个T f3:取一个T和一个double,返回另一个T 我希望避免向下投射和任何其他动态键入。我也希望尽可能避免突变 1:基于抽象类的尝试 2:参数多态性和辅助类 (TImpl的所有实现类都将是单例类): 抽象类TImpl{

我希望标题听起来不要太主观;我绝对不是要就OO展开一般性的辩论。我只想讨论解决以下问题的不同方法的基本利弊

让我们举一个最简单的例子:您想用函数来表示抽象数据类型T,这些函数可以将T作为输入、输出或两者兼而有之:

  • f1:取一个T,返回一个int
  • f2:获取一个字符串,返回一个T
  • f3:取一个T和一个double,返回另一个T
我希望避免向下投射和任何其他动态键入。我也希望尽可能避免突变

1:基于抽象类的尝试 2:参数多态性和辅助类 (TImpl的所有实现类都将是单例类):

抽象类TImpl{
抽象int f1(T);
抽象T f2(字符串s);
摘要T f3(T T,双d);
}
我们不再表示某个具体类型实际上实现了我们的原始规范——一个实现只是一个类型Foo,我们碰巧有一个TImpl实例。这似乎不是问题:如果您想要一个在任意实现上工作的函数,您只需执行以下操作:

// Say we want to return a Bar given an arbitrary implementation of our abstract type
Bar bar<T>(TImpl<T> ti, T t);
//假设给定抽象类型的任意实现,我们希望返回一个Bar
棒材(TImpl-ti,T);
在这一点上,我们不妨跳过继承和单例,使用

3一级功能表
class/*或struct,偶数*/TDict{
只读函数f1;
只读函数f2;
只读功能f3;
TDict(…){
这是1.f1=f1;
这是0.f2=f2;
这是f3=f3;
}
}
棒材(TDict-td;T);
虽然我看不出2和3之间有什么实际区别

示例实现
类MyT{
/*这里是原始数据结构;这个类不需要任何方法*/
}
//我们把下面的内容放在哪里并不重要;可以是MyT的静态方法,也可以是收集字典的静态类
静态只读TDict MyTDict
=新TDict(
(t) =>/*f1的主体在这里*/,,
//f2
(s) =>/*f2主体位于此处*/,,
//f3
(t,d)=>/*f3的主体位于此处*/
);

想法#3是单色的,但它看起来相当安全和干净。一个问题是它是否存在任何性能问题。我通常不需要动态调度,我更希望这些函数体静态内联到具体实现类型静态已知的地方。在这方面#2更好吗?

嗯。那么,让我看看我是否理解这一点:在具有隐式、初始参数即席多态性的OOP语言中,您希望使用参数多态性,以便通过具体化的方法字典使用具有显式即席多态性的准OO系统,所有这些都是为了允许分派的类型出现在函数签名中的其他位置,而不是隐式
this
参数。你想知道这是不是个好主意

从你的用户名,我很确定你非常清楚你真正想要的是la Wadler等人的类型。仅仅因为微软签署了SPJ的工资单并不意味着用C#写Haskell是个好主意

您的代码对于理解所表达的习惯用法的人来说非常清晰,但它远远超出了面向对象编程风格的主流,因此您需要确保在简洁性和正确性方面所取得的进步值得使用外国习惯用法的缺点,例如,使其他C#程序员感到困惑。我还建议通过分析一些概念验证代码来处理性能问题,因为这种风格与大多数C#程序中的风格相去甚远

另一方面,寻找干净的方式来表达外国习语并不是天生的坏事——例如,将代数数据类型的使用与访问者模式进行比较——因此,如果您需要具有这些属性的代码,而这是表达这些属性的最佳方式,请留下一些关于代码背后意图的注释,然后继续使用

简言之:确保它解决了您的问题,测试和分析以确保它不会导致其他问题,然后为其他程序员记录和解释解决方案。

您永远不会让C像Haskell一样工作,因为它强烈地偏离非方法函数,但我会试着把你的意图翻译成惯用的C。f1和f3使用常规的特殊多态性进行处理。例如,考虑:

interface INumber
{
    int Sign { get; }
    INumber Scale(double amount);
}
现在我们有了一个抽象数据类型,它具有您想要的两个特性:
Sign
是f1,
Scale()
是f3。f2是一个抽象构造函数,更为复杂。典型的OOP解决方案是使用工厂,一个封装构造函数的抽象类:

interface INumberFactory
{
    INumber Parse(string text);
}
Parse()
当然是f2。将这两个接口结合在一起,我认为我们已经涵盖了您的所有功能,而无需使用任何具体类型。这就是你要找的吗

编辑:回复FunctorSala的评论:

如果希望对函数返回的类型进行更精确的控制,典型的(但不是那么常见的)OOP解决方案是。您可以按如下方式定义接口,而不是上述方式:

interface INumber<T> where T : INumber<T>
{
    int Sign { get; }
    T Scale(double amount);
}

interface INumberFactory<T> where T : INumber<T>
{
    T Parse(string text);
}
接口INumber,其中T:INumber
{
int符号{get;}
T标度(双倍量);
}
接口INumberFactory,其中T:INumber
{
T解析(字符串文本);
}
这看起来有点奇怪,但它允许您实例化类型,以保证它们返回自己的类型,而不一定是基类型。例如:

class Rational : INumber<Rational>
{
    public int Sign { get { /* ... */ } }
    public Rational Scale(double amount) { /* ... */ }
}

class RationalFactory : INumberFactory<Rational>
{
    Rational Parse(string text);
}
类Rational:INumber
{
公共整数符号{get{/*…*/}
公共合理规模(双倍金额){/*…*/}
}
类别分配:INumberFactory
{
理性分析(字符串文本);
}
当然,这样做的缺点是,您的抽象类型现在需要一个类型参数,因此您不能再传递“raw”
INumber
INumberFact
interface INumber
{
    int Sign { get; }
    INumber Scale(double amount);
}
interface INumberFactory
{
    INumber Parse(string text);
}
interface INumber<T> where T : INumber<T>
{
    int Sign { get; }
    T Scale(double amount);
}

interface INumberFactory<T> where T : INumber<T>
{
    T Parse(string text);
}
class Rational : INumber<Rational>
{
    public int Sign { get { /* ... */ } }
    public Rational Scale(double amount) { /* ... */ }
}

class RationalFactory : INumberFactory<Rational>
{
    Rational Parse(string text);
}