在.NET Framework中,实现(override中)Equals(object)和GetHashCode()浮点型(System.Double和System.Single)是错误的.引用MSDN GetHashCode(object)规范:
哈希函数必须具有以下属性:
•如果两个对象比较相等,则每个对象的GetHashCode方法必须返回相同的值.但是,如果两个对象的比较不相等,则两个对象的GetHashCode方法不必返回不同的值.
如果NaN使用具有不同二进制表示的两个值,则这两个对象在该Equals方法下比较相等,但哈希码(几乎总是)是不同的.
现在,Microsoft Connect 已报告此错误.但他们为什么不解决这个问题?
修复很简单:让不同的NaN 不比较相同,或选择一个固定的哈希代码返回任何NaN.
修复不会破坏任何东西:今天的方式,当使用不同的东西时没有任何作用NaN.
你能想出任何不解决这个问题的理由吗?
这是一个说明当前行为的简单示例:
using System;
using System.Collections.Generic;
using System.Linq;
static class Program
{
const int setSize = 1000000; // change to higher value if you want to waste even more memory
const double oneNaNToRuleThemAll = double.NaN;
static readonly Random randomNumberGenerator …Run Code Online (Sandbox Code Playgroud) 我知道params修饰符(将数组类型的一个参数转换为所谓的"参数数组")特别不是方法签名的一部分.现在考虑这个例子:
class Giraffid
{
public virtual void Eat(int[] leaves)
{
Console.WriteLine("G");
}
}
class Okapi : Giraffid
{
public override void Eat(params int[] leaves)
{
Console.WriteLine("O");
}
}
Run Code Online (Sandbox Code Playgroud)
编译时没有任何警告.然后说:
var okapi = new Okapi();
okapi.Eat(2, 4, 6); // will not compile!
Run Code Online (Sandbox Code Playgroud)
给出一个错误(No overload for method 'Eat' takes 3 arguments).
现在,我知道编译器会将params修改器转换为System.ParamArrayAttribute相关参数的应用程序.通常,将一个属性集合应用于虚方法的参数,然后在具有不同属性集的派生类中的重写方法中装饰"对应"参数是没有问题的.
然而,编译器选择params静默忽略我的关键字.相反,如果一个使它反过来,并且应用于params基类中的参数Giraffid,然后省略覆盖中的关键字Okapi,编译器选择用它来装饰两个方法System.ParamArrayAttribute.当然,我用IL DASM验证了这些东西.
我的问题:
这是记录在案的行为?我已经彻底搜索了C#语言规范,但没有发现任何提及.
我可以说至少Visual Studio开发环境对此感到困惑.当键入2, 4, 6在上述方法的调用,所述 …
理解关于重载解析的C#语言规范显然很难,现在我想知道为什么这个简单的情况失败了:
void Method(Func<string> f)
{
}
void Method(Func<object> f)
{
}
void Call()
{
Method(() => { throw new NotSupportedException(); });
}
Run Code Online (Sandbox Code Playgroud)
这给出了编译时错误CS0121,以下方法或属性之间的调用是不明确的:后跟我的两个Method函数成员(重载).
我本来期望的是,Func<string>是一个更好的转换目标比Func<object>,然后应使用第一个重载.
由于.NET 4和C#4(2010),通用委托类型Func<out TResult>已协变中TResult,并且由于该原因的隐式转换从存在Func<string>于Func<object>同时明确的隐式转换可以从存在Func<object>于Func<string>.那么它会产生Func<string>更好的转换目标,而重载分辨率应该选择第一个过载?
我的问题很简单:我在这里错过了C#规范的哪一部分?
增加:这很好用:
void Call()
{
Method(null); // OK!
}
Run Code Online (Sandbox Code Playgroud) 在数学上,考虑这个问题的有理数
8725724278030350 / 2**48
Run Code Online (Sandbox Code Playgroud)
其中**在分母表示取幂,即,分母是2对48次方.(分数不在最低方面,通过2可还原的)此数目是恰好作为表示的System.Double.它的十进制扩展是
31.0000000000000'49'73799150320701301097869873046875 (exact)
Run Code Online (Sandbox Code Playgroud)
撇号不代表缺失的数字,而只是标记圆形到15的分数.将执行17位数字.
请注意以下内容:如果此数字四舍五入为15位,则结果为31(后跟十三0秒),因为下一个数字(49...)以a开头4(意味着向下舍入).但如果数字首先四舍五入为17位,然后四舍五入为15位,结果可能是31.0000000000001.这是因为第一次舍入通过将49...数字增加到50 (terminates)(下一个数字73...)而向上舍入,然后第二次舍入可能再次向上舍入(当中点舍入规则表示"从零开始舍入"时).
(当然,还有更多具有上述特征的数字.)
现在,事实证明.NET的这个数字的标准字符串表示是"31.0000000000001".问题:这不是一个错误吗?通过标准字符串表示,我们指的String是由参数Double.ToString()实例方法产生的,当然,该方法与生成的方法相同ToString("G").
需要注意的一个有趣的事情是,如果你投的上述号码System.Decimal,然后你得到一个decimal是31准确!请参阅此Stack Overflow问题,以讨论将a转换Double为Decimal包含第一个舍入到15位的令人惊讶的事实.这意味着转换为Decimal正确的舍入到15位数,而调用ToSting()是不正确的.
总而言之,我们有一个浮点数,当输出给用户时,是31.0000000000001,但当转换为Decimal …
结构System.DateTime及其表兄System.DateTimeOffset的结构布局类型设置为"自动".这可以看作:
typeof(DateTime).IsAutoLayout /* true */
Run Code Online (Sandbox Code Playgroud)
要么:
typeof(DateTime).StructLayoutAttribute.Value /* Auto */
Run Code Online (Sandbox Code Playgroud)
或者从IL中可以看出它声明:
.class public auto ansi serializable sealed beforefieldinit System.DateTime
¯¯¯¯
Run Code Online (Sandbox Code Playgroud)
通常,使用C#编写的结构(即不是枚举的.NET值类型)将具有"顺序"布局(除非StructLayoutAttribute已应用指定另一个布局).
我通过一些常见的搜索BCL组件,以及DateTime和DateTimeOffset是唯一公开可见的结构,我发现用这个布局.
有谁知道为什么DateTime这个不寻常的结构布局?
我们相信这个例子在C#编译器中出现了一个错误(如果我们错了,请取笑我).这个错误可能是众所周知的:毕竟,我们的示例是对此博客文章中描述的内容的简单修改.
using System;
namespace GenericConflict
{
class Base<T, S>
{
public virtual int Foo(T t)
{ return 1; }
public virtual int Foo(S s)
{ return 2; }
public int CallFooOfT(T t)
{ return Foo(t); }
public int CallFooOfS(S s)
{ return Foo(s); }
}
class Intermediate<T, S> : Base<T, S>
{
public override int Foo(T t)
{ return 11; }
}
class Conflict : Intermediate<string, string>
{
public override int Foo(string t)
{ return 101; }
} …Run Code Online (Sandbox Code Playgroud) c# compiler-construction generics overriding overload-resolution
从.NET 4.5(2012)开始,一些新的扩展方法出现在System.Reflection.RuntimeReflectionExtensions类中.然而,新方法似乎没有给我们任何新的东西.一个例子:
static void Main()
{
var prop1 = typeof(string).GetProperty("Length");
var prop2 = typeof(string).GetRuntimeProperty("Length"); // extension, needs: using System.Reflection;
Console.WriteLine(prop1 == prop2);
Action a = Main;
var meth1 = a.Method;
var meth2 = a.GetMethodInfo(); // extension, needs: using System.Reflection;
Console.WriteLine(meth1 == meth2);
}
Run Code Online (Sandbox Code Playgroud)
这写了True两次.
(==操作符在这里超载,但甚至检查参考相等性(object)prop1 == (object)prop2和(object)meth1 == (object)meth2给出True).
那么这些新的公开可见方法的目的是什么?显然,我必须忽略或误解某些东西.
像往常一样,int?意味着System.Nullable<int>(或System.Nullable`1[System.Int32]).
假设你有一个内存IEnumerable<int?>(例如一个List<int?>),让我们称之为seq; 然后你可以找到它的总和:
var seqSum = seq.Sum();
Run Code Online (Sandbox Code Playgroud)
当然这是扩展方法重载int? IEnumerable<int?>.Sum()(文档),它实际上是一个静态方法System.Linq.Enumerable.
但是,该方法永远不会返回null,为什么返回类型声明为Nullable<>?即使在seq空集合或更一般地集合的所有元素都是null类型的值的情况下int?,所讨论的Sum方法仍然返回零,而不是null.
这从文档中可以看出,也可以从System.Core.dll源代码中看出:
public static int? Sum(this IEnumerable<int?> source) {
if (source == null) throw Error.ArgumentNull("source");
int sum = 0;
checked {
foreach (int? v in source) {
if (v != null) sum += v.GetValueOrDefault();
}
}
return sum; …Run Code Online (Sandbox Code Playgroud) 背景:在属性规范中,有时存在两种写入应用属性的有效方法。例如,如果属性类具有名称HorseAttribute,则可以将属性应用为[HorseAttribute]或[Horse]。歧义可以用@例如来解决[@Horse]。
以下是有效的程序:
using System;
using Alpha;
using Beta;
namespace N
{
[Horse]
class C
{
}
}
namespace Alpha
{
// valid non-abstract attribute type with accessible constructor
class HorseAttribute : Attribute
{
}
}
namespace Beta
{
// any non-attribute type with that name
enum Horse
{
}
}
Run Code Online (Sandbox Code Playgroud)
Alpha.HorseAttribute我编写just时,C#编译器可以选择[Horse]。毕竟,类型Beta.Horse完全不适合在属性规范中使用。
即使交换名称,C#编译器也会知道该怎么做:
using System;
using Alpha;
using Beta;
namespace N
{
[Horse] …Run Code Online (Sandbox Code Playgroud) 请考虑以下代码:
DateTime t = DateTime.Today;
bool isGreater = t > null;
Run Code Online (Sandbox Code Playgroud)
使用Visual Studio 2010(C#4,.NET 4.0),我收到以下警告:
警告CS0458:表达式的结果始终为'bool'类型的'null'
这是不正确的; 结果总是false(类型bool):
现在,struct DateTime重载>(大于)运算符.任何不可为空的结构(如DateTime)都可以隐式转换为相应的Nullable<>类型.上面的表达式完全相同
bool isGreater = (DateTime?)t > (DateTime?)null;
Run Code Online (Sandbox Code Playgroud)
这也产生了同样的错误警告.在这里,>操作员是提升的操作员.如果HasValue其两个操作数中的任何一个是,则返回false false.否则,提升的运算符将继续将两个操作数展开到底层结构,然后调用该>结构定义的重载(但在这种情况下,这不是必需的,其中一个操作数不是HasValue).
你能重现这个bug,这个bug是众所周知的吗?我误解了什么吗?
对于所讨论int的运算符重载的所有结构类型(不是简单类型,而不是枚举类型),这是相同的.
(现在如果我们使用==而不是>,那么一切都应该完全相似(因为DateTime也会使==运算符超载).但它不相似.如果我说
DateTime t = DateTime.Today;
bool isEqual = t == null;
Run Code Online (Sandbox Code Playgroud)
我没有得到警告☹有时候你会看到人们不小心检查变量或参数为null,没有意识到他们的变量类型是一个结构(它过载==而且不是一个简单的类型int).如果他们得到警告会更好.)
更新:使用Visual Studio 2015的C#6.0编译器(基于 Roslyn …
c# ×10
.net ×4
nullable ×2
overriding ×2
.net-4.5 ×1
comparison ×1
covariance ×1
datetime ×1
delegates ×1
generics ×1
hash ×1
linq ×1
nan ×1
overloading ×1
params ×1
reflection ×1
rounding ×1
struct ×1
structlayout ×1
sum ×1