为什么这个断言在比较结构时抛出格式异常

本文关键字:格式 异常 结构 比较 断言 为什么 | 更新日期: 2023-09-27 18:00:05

我试图断言两个System.Drawing.Size结构的相等性,但我得到了一个格式异常,而不是预期的断言失败。

[TestMethod]
public void AssertStructs()
{
    var struct1 = new Size(0, 0);
    var struct2 = new Size(1, 1);
    //This throws a format exception, "System.FormatException: Input string was not in a correct format."
    Assert.AreEqual(struct1, struct2, "Failed. Expected {0}, actually it is {1}", struct1, struct2); 
    //This assert fails properly, "Failed. Expected {Width=0, Height=0}, actually it is {Width=1, Height=1}".
    Assert.AreEqual(struct1, struct2, "Failed. Expected " + struct1 + ", actually it is " + struct2); 
}

这是故意的行为吗?我是不是做错了什么?

为什么这个断言在比较结构时抛出格式异常

我找到了。是的,这是一个bug。

问题是这里有两个级别的string.Format

第一个级别的格式化类似于:

string template  = string.Format("Expected: {0}; Actual: {1}; Message: {2}",
                                 expected, actual, message);

然后我们使用string.Format和您提供的参数:

string finalMessage = string.Format(template, parameters);

(很明显,这里提供了培养物,还有一些类型的消毒……但还不够。)

这看起来很好——除非期望值和实际值本身在转换为字符串后以大括号结束——Size就是这样。例如,您的第一个尺寸最终被转换为:

{Width=0, Height=0}

因此,第二级格式化类似于:

string.Format("Expected: {Width=0, Height=0}; Actual: {Width=1, Height=1 }; " +
              "Message = Failed expected {0} actually is {1}", struct1, struct2);

这就是失败的地方。哎哟

事实上,我们可以很容易地证明这一点,方法是欺骗格式化,将我们的参数用于预期和实际部分:

var x = "{0}";
var y = "{1}";
Assert.AreEqual<object>(x, y, "What a surprise!", "foo", "bar");

结果是:

Assert.AreEqual failed. Expected:<foo>. Actual:<bar>. What a surprise!

显然坏了,因为我们没有预料到foo,实际值bar也没有!

基本上,这就像SQL注入攻击,但在string.Format的上下文中并不那么可怕。

作为一种变通方法,您可以按照StriplingWarrior的建议使用string.Format。这避免了对使用实际/预期值进行格式化的结果执行第二级格式化。

我想您发现了一个错误。

这是有效的(抛出断言异常):

var a = 1;
var b = 2;
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

这是有效的(输出消息):

var a = new{c=1};
var b = new{c=2};
Console.WriteLine(string.Format("Not equal {0} {1}", a, b));

但这不起作用(抛出FormatException):

var a = new{c=1};
var b = new{c=2};
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

我想不出有什么理由会出现这种预期行为。我会提交一份错误报告。与此同时,这里有一个变通方法:

var a = new{c=1};
var b = new{c=2};
Assert.AreEqual(a, b, string.Format("Not equal {0} {1}", a, b));

我同意@StriplingWarrior的观点,认为这确实是Assert的一个bug。至少有2个重载上的AreEqual()方法。正如StiplingWarrior已经指出的,以下是失败的;

var a = new { c = 1 };
var b = new { c = 2 };
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

我已经在这方面做了一些实验,进一步提高代码使用的明确性。以下内容也不起作用;

// specify variable data type rather than "var"...no effect, still fails
Size a = new Size(0, 0);
Size b = new Size(1, 1);
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

// specify variable data type and name the type on the generic overload of AreEqual()...no effect, still fails
Size a = new Size(0, 0);
Size b = new Size(1, 1);
Assert.AreEqual<Size>(a, b, "Not equal {0} {1}", a, b);

这让我思考。系统绘画Size是一个结构。物体呢?param list确实指定string消息之后的列表为params object[]。从技术上讲,yes结构对象。。。但对象的特殊类型,即值类型。我认为这就是漏洞所在。如果我们使用与Size具有类似用法和结构的自己的对象,那么以下内容实际上是起作用的;

private class MyClass
{
    public MyClass(int width, int height)
        : base()
    { Width = width; Height = height; }
    public int Width { get; set; }
    public int Height { get; set; }
}
[TestMethod]
public void TestMethod1()
{
    var test1 = new MyClass(0, 0);
    var test2 = new MyClass(1, 1);
    Assert.AreEqual(test1, test2, "Show me A [{0}] and B [{1}]", test1, test2);
}

我认为第一个断言不正确。

使用这个替代:

Assert.AreEqual(struct1, 
                struct2, 
                string.Format("Failed expected {0} actually is {1}", struct1, struct2));