我收集了一些极端案例和脑筋急转弯,总是想听到更多。这个页面只涵盖了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上尝试过……)
只是为了澄清,这只是我正在寻找的事情的一个例子-我并不是特别寻找对这个奇怪现象的讨论/解释。(这和普通的弦乐实习不一样;特别地,当调用构造函数时,字符串实习通常不会发生。)我真的是在要求类似的奇怪行为。
还有其他的宝藏吗?
c#支持数组和列表之间的转换,只要数组不是多维的,并且类型之间有继承关系,并且类型是引用类型
object[] oArray = new string[] { "one", "two", "three" };
string[] sArray = (string[])oArray;
// Also works for IList (and IEnumerable, ICollection)
IList<string> sList = (IList<string>)oArray;
IList<object> oList = new string[] { "one", "two", "three" };
注意,这是无效的:
object[] oArray2 = new int[] { 1, 2, 3 }; // Error: Cannot implicitly convert type 'int[]' to 'object[]'
int[] iArray = (int[])oArray2; // Error: Cannot convert type 'object[]' to 'int[]'
银行家的舍入。
这不是一个编译器错误或故障,但肯定是一个奇怪的极端情况…
. net框架采用了一种被称为银行家舍入的方案或舍入。
在银行家的四舍五入中,0.5的数字四舍五入到最接近的偶数,所以
Math.Round(-0.5) == 0
Math.Round(0.5) == 0
Math.Round(1.5) == 2
Math.Round(2.5) == 2
etc...
这可能会导致基于更广为人知的四舍五入的财务计算中出现一些意想不到的错误。
Visual Basic也是如此。
如果您有一个泛型类,它的方法可以根据类型参数而变得模糊,该怎么办?我最近在写一本双向词典时遇到了这种情况。我想要编写对称的Get()方法,它将返回传递的任何参数的相反值。就像这样:
class TwoWayRelationship<T1, T2>
{
public T2 Get(T1 key) { /* ... */ }
public T1 Get(T2 key) { /* ... */ }
}
如果你创建一个实例,其中T1和T2是不同类型的,那么一切都很好:
var r1 = new TwoWayRelationship<int, string>();
r1.Get(1);
r1.Get("a");
但如果T1和T2是相同的(可能如果一个是另一个的子类),这是一个编译器错误:
var r2 = new TwoWayRelationship<int, int>();
r2.Get(1); // "The call is ambiguous..."
有趣的是,第二种情况下的所有其他方法仍然可用;只有调用现在模棱两可的方法才会导致编译器错误。有趣的案例,只是有点不太可能和晦涩。
在我们使用的API中,返回域对象的方法可能返回一个特殊的“空对象”。在此实现中,比较运算符和Equals()方法将被重写,如果与null进行比较则返回true。
所以这个API的用户可能会有这样的代码:
return test != null ? test : GetDefault();
或者更啰嗦一点,像这样:
if (test == null)
return GetDefault();
return test;
其中GetDefault()是一个方法,返回一些我们想要使用的默认值,而不是null。当我使用ReSharper并按照它的建议重写这其中的任何一个时,我感到惊讶:
return test ?? GetDefault();
如果测试对象是从API返回的空对象,而不是一个正确的空对象,那么代码的行为现在已经改变,因为空合并操作符实际上检查null,而不是运行operator=或Equals()。
以下是我的一些建议:
当调用实例方法而不抛出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();
}
}
这在大多数语言中都是正确的,只要调用实例方法时不使用对象的状态。只有在访问对象的状态时才解除引用