我正在学习C#并且在Pex for fun网站上一直在搞乱。该网站向您提出重新实施秘密算法的挑战,方法是在站点中键入代码并检查输入和输出在您的实现和秘密实现之间的差异。
无论如何,我遇到了一个名为XAndY的基本代码决斗。
从名称来看,答案显而易见:
public static bool Puzzle(bool x, bool y)
{
return x && y;
}
然而,这是不正确的,Pex告诉我,以下输入产生的结果与秘密实施不同:
输入:
x:true y:true(0x02)
输出:
我的实现:true(0x02)
秘密实施:假
不匹配你的拼图方法产生了错误的结果。
代码: 拼图(true,PexSafeHelpers.ByteToBoolean((byte)2));
在尝试比较不同类型的true之后出现了很多困惑之后,我意识到Pex正在寻找的实现实际上只是使用了按位AND:
return x & y;
我认为,出于语义和短路的原因,您应该使用逻辑&&
来比较布尔值,但无论如何:
x & y
和x && y
明确地不对所有可能的bool参数都有相同的输出? (或者它可能是Pex中的一些东西?)true
的不同值?如果是这样,怎么样?答案 0 :(得分:17)
这个难题正在利用我认为是C#编译器中的错误。 (该bug也会影响VB.NET。)
在C#5.0规范中,§4.1.8表示“类型bool
的可能值为true
和false
”,而§7.11.3表示{{1是一个逻辑运算符:
如果
operator &(bool x, bool y)
和x & y
都是true
,则x
的结果为y
。否则,结果为true
。
这显然违反了false
生成true & true
的规范。发生了什么事?
在运行时,false
由1字节整数表示。 C#编译器使用0表示bool
,使用1表示false
。为了实现true
运算符,C#编译器在生成的IL中发出按位 &
指令。乍一看,这似乎没问题:涉及0和1的按位AND
操作完全对应于涉及AND
和AND
的逻辑false
操作。
但是,CLI specification的§III.1.1.2明确允许true
由0或1以外的整数表示:
CLI布尔类型在内存中占用1个字节。全零的位模式表示值false。设置了任何一个或多个位的位模式(类似于非零整数)表示值为true。
超出C#的范围,创建bool
确实可能 - 而且完全合法 - 其值为2,从而导致bool
出乎意料地行为。这就是Pex网站正在做的事情。
以下是演示:
&
所以问题的答案是:
using System;
using System.Reflection.Emit;
class Program
{
static void Main()
{
DynamicMethod method =
new DynamicMethod("ByteToBoolean", typeof(bool), new[] { typeof(byte) });
ILGenerator il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // Load the byte argument...
il.Emit(OpCodes.Ret); // and "cast" it directly to bool.
var byteToBoolean =
(Func<byte, bool>)method.CreateDelegate(typeof(Func<byte, bool>));
bool x = true;
bool y = byteToBoolean(2);
Console.WriteLine(x); // True
Console.WriteLine(y); // True
Console.WriteLine(x && y); // True
Console.WriteLine(x & y); // False (!) because 1 & 2 == 0
Console.WriteLine(y.Equals(false)); // False
Console.WriteLine(y.Equals(true)); // False (!) because 2 != 1
}
}
和x & y
可能有不同的值。但是,此行为违反了C#规范。x && y
(如上所示)来区分Boolean.Equals
值。但是,此行为违反了true
。答案 1 :(得分:1)
&
值,C#中的 Boolean
不是按位运算符。它超载了。运算符有两个完全独立的实现。如果输入是布尔值,则为非短路逻辑布尔运算符;如果值为非布尔值,则为按位AND。
在您显示的代码中,输入是一个布尔变量。它不是一个数值,它不是一个解析为布尔值(可能有副作用)的表达式,或者其他任何东西。
当输入是两个布尔变量时,&
和&&
之间的输出永远不会有任何不同。在这两者之间存在任何可观察差异的唯一方法是使布尔表达式比仅将变量解析为其值或某些非布尔输入更复杂。
如果操作数可以是除bool
之外的某种类型,则提供对任一运算符具有不同结果的类型非常简单,例如覆盖true
运算符的超级均值类型与庄园不一致的庄园隐含转换为bool
:
答案 2 :(得分:0)
我注意到Roslyn编译器小组遇到此问题has been raised。 Further discussion。
它具有以下分辨率:
这是有效设计的,您可以找到文档的详细信息 在这里: https://github.com/dotnet/roslyn/blob/master/docs/compilers/Boolean%20Representation.md
您可以在此处查看更多详细信息和有关此的以往对话: #24652
说明文件指出:
布尔值的表示形式
C#和VB编译器代表true(True)和false(False)布尔值 (布尔)值,其单字节值分别为1和0, 并假设他们正在使用的任何布尔值是 仅限于由这两个基础值表示。的 ECMA 335 CLI规范允许将“ true”布尔值设置为 用任何非零值表示。如果您使用具有 除0或1以外的基础表示形式,您可能会遇到意外情况 结果。这可能发生在C#中不安全的代码中,或者通过互操作 使用允许其他值的语言。 为避免这些意外情况 结果,归一化是程序员的责任 传入值。
(所有斜体均为我自己强调)