我收集了一些极端案例和脑筋急转弯,总是想听到更多。这个页面只涵盖了c#语言的一些细节,但我也发现了。net核心的东西也很有趣。例如,这里有一个没有在页面上,但我觉得不可思议:
string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));
我希望输出False -毕竟,“new”(具有引用类型)总是创建一个新对象,不是吗?c#和CLI的规范都表明应该这样做。嗯,在这个特殊情况下不是这样。它输出True,并且在我测试过的框架的每个版本上都是这样。(不可否认,我还没有在Mono上尝试过……)
只是为了澄清,这只是我正在寻找的事情的一个例子-我并不是特别寻找对这个奇怪现象的讨论/解释。(这和普通的弦乐实习不一样;特别地,当调用构造函数时,字符串实习通常不会发生。)我真的是在要求类似的奇怪行为。
还有其他的宝藏吗?
几年前,在制定忠诚度计划时,我们遇到了一个关于给予客户积分数量的问题。这个问题与将double类型转换为int类型有关。
代码如下:
double d = 13.6;
int i1 = Convert.ToInt32(d);
int i2 = (int)d;
i1 == i2吗?
结果是i1 != i2。
由于Convert和cast运算符的舍入策略不同,实际值为:
i1 == 14
i2 == 13
调用Math. ceiling()或Math. floor()(或Math. ceiling())总是更好。用符合我们要求的midpointrsurround进行四舍五入)
int i1 = Convert.ToInt32( Math.Ceiling(d) );
int i2 = (int) Math.Ceiling(d);
如果作为Rec(0)调用(不在调试器下),该函数将做什么?
static void Rec(int i)
{
Console.WriteLine(i);
if (i < int.MaxValue)
{
Rec(i + 1);
}
}
答:
在32位JIT上,它应该导致StackOverflowException
在64位JIT上,它应该将所有数字打印为int。MaxValue
这是因为64位JIT编译器应用尾部调用优化,而32位JIT则没有。
不幸的是,我手头没有一台64位机器来验证这一点,但该方法确实满足尾部调用优化的所有条件。如果有人有的话,我很想看看这是不是真的。
有趣的是,当我第一次看到它时,我认为这是c#编译器正在检查的东西,但即使你直接发出IL来消除任何干扰的机会,它仍然会发生,这意味着它真的是newobj操作代码正在进行检查。
var method = new DynamicMethod("Test", null, null);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Newarr, typeof(char));
il.Emit(OpCodes.Newobj, typeof(string).GetConstructor(new[] { typeof(char[]) }));
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Newarr, typeof(char));
il.Emit(OpCodes.Newobj, typeof(string).GetConstructor(new[] { typeof(char[]) }));
il.Emit(OpCodes.Call, typeof(object).GetMethod("ReferenceEquals"));
il.Emit(OpCodes.Box, typeof(bool));
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(object) }));
il.Emit(OpCodes.Ret);
method.Invoke(null, null);
如果检查string,它也等于true。这意味着这个操作代码必须有特殊的行为来实习空字符串。
即使枚举函数重载,它们也应该使0为整数。
我知道c#核心团队将0映射到enum的基本原理,但是,它仍然没有像它应该的那样正交。来自Npgsql的例子。
测试的例子:
namespace Craft
{
enum Symbol { Alpha = 1, Beta = 2, Gamma = 3, Delta = 4 };
class Mate
{
static void Main(string[] args)
{
JustTest(Symbol.Alpha); // enum
JustTest(0); // why enum
JustTest((int)0); // why still enum
int i = 0;
JustTest(Convert.ToInt32(0)); // have to use Convert.ToInt32 to convince the compiler to make the call site use the object version
JustTest(i); // it's ok from down here and below
JustTest(1);
JustTest("string");
JustTest(Guid.NewGuid());
JustTest(new DataTable());
Console.ReadLine();
}
static void JustTest(Symbol a)
{
Console.WriteLine("Enum");
}
static void JustTest(object o)
{
Console.WriteLine("Object");
}
}
}
这是我无意中遇到的最奇怪的事情:
public class DummyObject
{
public override string ToString()
{
return null;
}
}
用途如下:
DummyObject obj = new DummyObject();
Console.WriteLine("The text: " + obj.GetType() + " is " + obj);
将抛出NullReferenceException。事实证明,c#编译器将多个添加编译为对String.Concat(object[])的调用。在。net 4之前,在Concat重载中有一个错误,其中对象被检查为null,而不是ToString()的结果:
object obj2 = args[i];
string text = (obj2 != null) ? obj2.ToString() : string.Empty;
// if obj2 is non-null, but obj2.ToString() returns null, then text==null
int length = text.Length;
这是ECMA-334§14.7.4的错误:
当一个或两个操作数都是字符串类型时,binary +操作符执行字符串连接。如果字符串连接的操作数为空,则替换为空字符串。否则,任何非字符串操作数都将通过调用继承自type object的虚拟ToString方法转换为其字符串表示形式。如果ToString返回null,则替换为空字符串。
以下是我的一些建议:
当调用实例方法而不抛出NullReferenceException时,此值可以为null
不必为枚举定义默认枚举值
首先简单一点:
enum NoZero
{
Number = 1
}
public bool ReturnsFalse()
{
//The default value is not defined!
return Enum.IsDefined(typeof (NoZero), default(NoZero));
}
下面的代码实际上可以打印真!
internal sealed class Strange
{
public void Foo()
{
Console.WriteLine(this == null);
}
}
一段简单的客户端代码将导致这样的结果
HelloDelegate(奇怪的条);
public class Program
{
[STAThread()]
public static void Main(string[] args)
{
Strange bar = null;
var hello = new DynamicMethod("ThisIsNull",
typeof(void), new[] { typeof(Strange) },
typeof(Strange).Module);
ILGenerator il = hello.GetILGenerator(256);
il.Emit(OpCodes.Ldarg_0);
var foo = typeof(Strange).GetMethod("Foo");
il.Emit(OpCodes.Call, foo);
il.Emit(OpCodes.Ret);
var print = (HelloDelegate)hello.CreateDelegate(typeof(HelloDelegate));
print(bar);
Console.ReadLine();
}
}
这在大多数语言中都是正确的,只要调用实例方法时不使用对象的状态。只有在访问对象的状态时才解除引用