【问题标题】:How to translate this Javascript snippet to C# [closed]如何将此 Javascript 片段转换为 C# [关闭]
【发布时间】:2019-09-04 22:23:21
【问题描述】:

Javascript:

function bounds(a, b, c) {
  null != b && (a = Math.max(a, b));
  null != c && (a = Math.min(a, c));
  return a
}

老实说,我什至不明白这里发生了什么。如果b 不为空,则a 被设置为Math.max(a, b)。如果c 不为空,则aMath.min(a, c) 设置?

如何将其翻译成 C#?

附:这是我的翻译。这是正确的吗?

private double bounds(double a, double? b, double? c) {
    if (b != null) a = Math.Max(a, b.Value);
    if (c != null) a = Math.Min(a, c.Value);

    return a;
}

【问题讨论】:

  • 您的陈述“我什至不明白这里发生了什么”告诉您第一步应该是什么。 了解发生了什么。一旦您了解了程序 for 的用途,然后问自己“我将如何在 C# 中实现这些语义?”通过决定方法的签名是什么来启动那个过程。但当然不要对这种难以理解的混乱进行逐句翻译。
  • 您的翻译不正确。
  • 当您重写此代码时,请质疑您所做的每一个决定,着眼于提高代码的可读性。为什么变量的名称是abc,而不是程序中的意思?为什么要覆盖变量a,而不是创建具有新的、有意义的名称new 局部变量?当地人很便宜;使用它们!以此类推。
  • @EricLippert 翻译不正确怎么办?尽管有参数名称。
  • javascipt 有 Max 和 Min,但你在 C# 中使用了 Max & max

标签: javascript c#


【解决方案1】:

老实说,我什至不明白这里发生了什么。

我也没有。该代码似乎实现了合同“如果c 是最小元素,则返回它,否则,返回abc 的中间元素,以及一些未知目的的可空性检查。

这是一个奇怪的实现合同,它让我认为它可能是一个未经测试的“返回中间元素”的实现,它有一个错误;如果c 在实践中从来都不是最小的元素,那么这个错误可能永远不会被捕获。

如何将其翻译成 C#?

理解上下文中代码的含义,然后在惯用的 C# 中实现这些语义。理解是重要的部分;一旦你理解了代码,编写一个正确的、可测试的实现就很简单了。

这是我的翻译。这是正确的吗?

private double bounds(double a, double? b, double? c) {
  if (b != null) a = Math.Max(a, b.Value);
  if (c != null) a = Math.Min(a, c.Value);
  return a;
}

这是正确的,因为它准确地实现了原始代码的语义,可能有错误的代码。但也好不到哪里去。让我们改进它。突出三点:

  • 该方法不消耗this,所以应该是static
  • 该方法是 C# 方法,应遵循 C# 的命名约定。
  • 覆盖其形式的方法在设计时难以阅读,在运行时也难以调试。就此而言,任何改变变量的方法都比不改变变量的方法更难理解。
  • 变量名为 abc,对它们有意义的变量难以阅读,因为它们掩盖了这些含义。

让我们解决所有问题。

private static double Bounds(double a, double? b, double? c) 
{
  double larger = (b == null) ? a : Math.Max(a, b.Value);
  double smaller = (c == null) ? larger : Math.Min(larger, c.Value);
  return smaller;
}

abc 我们仍然没有好名字,但至少我们知道 largersmaller 反映了它们的含义。

我们完成了吗?总是问自己我怎样才能让这更简单

您可以通过将检查移到帮助程序中来使其更简单。

private static double Max(double x, double? y) =>
  (y == null) ? x : Math.Max(x, y.Value)

private static double Min(double x, double? y) =>
  (y == null) ? x : Math.Min(x, y.Value)

现在我们的方法变成了:

private static double Bounds(double a, double? b, double? c) 
{
  double larger = Max(a, b);
  double smaller = Min(larger, c);
  return smaller;
}

现在我们意识到我们可以将其重写为

private static double Bounds(double a, double? b, double? c) => 
  Min(Max(a, b), c);

目前还不清楚这个东西的目的是什么,但我希望你同意我的实现在它的作用上再清楚不过了。在我的实现中,很明显语义是“如果c 最少,则返回它,否则返回中间元素”。将我的版本的可理解性与原始的可怕混乱进行比较,看看哪一个更容易理解。

这里的教训是什么?

  • 将代码组织成单一赋值形式
  • 将复杂的逻辑移入辅助方法,将其抽象出来
  • 重写代码以消除不必要的解释变量。
  • 代码变得简洁易懂。

进一步的思考;假设我的猜想是对的,意图是这样写:

private static double Middle(double test, double? bottom, double? top)

方法的语义是:

  • bottomtop 可能都为空。
  • 如果两者都不为空,则bottom 必须是较小的。
  • 如果bottomtop都不为null,则返回中间的值。
  • 如果bottomtop 都为空,那么test 就是中间的值。
  • 如果bottom 为空但top 非空,则testtop 中较小的值是中间的值。在这里,我们将“null”视为底部的“负无穷大”。
  • 类似地,如果top 为空但bottom 不为空,则bottomtest 中较大的一个是中间的。这里的空顶部被视为“正无穷大”。

这实际上是一个非常明智的合约,但它要求我们知道bottom 总是小于top 当它们不都为空时。在 C# 中一个好的实现要么是 Debug.Assert 这个事实(如果方法是 private),要么是 throw 如果违反了先决条件(如果方法是 public)。

如果是这种情况,那么更好的选择是这样做:

private static double Middle(double test, double? bottom, double? top) =>
  Middle(
    test, 
    bottom ?? Double.NegativeInfinity, 
    top ?? Double.PositiveInfinity);

private static double Middle(double test, double bottom, double top) =>
  Math.Min(Math.Max(test, bottom), top);

(如果你不熟悉??,则读作“coalesce”,x??y表示“如果x不为空,则使用x.Value,否则使用y”。这是一个非常有用的运算符。)

再一次,看看当您在编写代码时考虑到可读性时代码看起来多么漂亮和简单。很明显,“null 表示底部为负无穷大,顶部为正无穷大”,因为这就是代码所说的!

或者,编写一个正确的 Middle 实现,不需要对三个输入进行任何排序:

// True if test is between b1 and b2, false otherwise
private static bool Between(double test, double b1, double b2) =>
  ((b1 <= test) & (test <= b2)) | ((b2 <= test) & (test <= b1));

// Returns the middle value of a, b, and c.
private static double Middle(double a, double b, double c)
{
  if (Between(a, b, c) return a;
  if (Between(b, a, c) return b;
  return c;
}

或者,如果你喜欢

private static double Middle(double a, double b, double c) =>
  Between(a, b, c) ? a : Between(b, a, c) ? b : c;

现在我们可以再次轻松地编写 Bounds 方法:

private static double Bounds(double test, double? bottom, double? top) =>
  Middle(
    test, 
    bottom ?? Double.NegativeInfinity, 
    top ?? Double.PositiveInfinity);

再一次,想想我们在这里努力的目标。 做一件事并且做得非常好的简单方法。一旦有了这些,您就可以使用这些方法来构建其他简单的方法,并逐渐增加程序的复杂性,而不会使其难以理解。

【讨论】:

    【解决方案2】:

    以下是 JavaScript 大致翻译的内容:

    function bounds(a, b, c) {
      // If "b" not null, then update "a" as the maximum of "a" and "b" 
      null != b && (a = Math.max(a, b));
    
      // If "c" not null, then update "a" as the maximum of "a" and "c" 
      null != c && (a = Math.min(a, c));
    
      return a
    }
    

    C# 等式可以写成:

    static double ? bounds(double ? a, double ? b, double ? c)
    {
        if (b.HasValue)
        {
            a = a.HasValue ? Math.Max(a.Value, b.Value) : a;
        }
    
        if (c.HasValue)
        {
            a = a.HasValue ? Math.Min(a.Value, c.Value) : a;
        }
    
        return a;
    }
    

    【讨论】:

    • 小数怎么可以为空?
    • 使用十进制?而不是小数。
    • 既然如此,为什么选择decimal?与 JavaScript 的 Number 最接近的类型是 double
    • 谢谢,但您真的还需要解决 Eric Lippert 的观点,这样它才能发挥作用。
    • 作为澄清,Microsoft 建议 decimal 用于专门代表货币的值(也称为价格、货币交易等)。 ? 运算符用于将 value 类型转换为引用类型,使它们可以为空。 intfloatdecimaldouble 等值类型都可以使用? 变为可空:int? j = null;
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-28
    • 2011-05-11
    • 2023-03-23
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    相关资源
    最近更新 更多