为什么T.TryParse不返回Nullable<T>?

18
据我了解,自 Framework 2.0 起就存在 int.TryParse(string, out int)。 同样也有 int?
是否有理由使用 out 参数而不是返回一个 int?,其中 HasValue 根据能否进行转换设置为 truefalse

3
这将是一个针对.NET框架开发团队的问题。大多数企业最终会编写一个名为ToNullableInt的扩展方法,实现所需的行为。如果未来版本的C#原生支持此功能,我不会感到意外。 - Rob Epstein
可能是因为其他语言没有这样简单的可空语法。例如,在C++/CLI中,你必须写成Nullable<int> result = int::TryParse(...) - Stephan
3
我不知道为什么过去没有这样做,但是 C# 6 中将会有一种新的解决方案——“声明表达式”,可以让你在不事先定义变量 myInt 的情况下就能够使用 int.TryParse(string, out int myInt) - Avner Shahar-Kashtan
在我看来,方法名中的“_Try_”部分意味着该方法将会指示“_Parse_”部分是否成功。这只是我的个人观点... - user1429080
2
我记得Eric Lippert在一篇博客文章中提到,如果他们今天构建.TryParse()方法,他们会返回Nullable<T>,但当时它不存在(或者至少不像现在这样好)。我会看看能否找到它。 - anaximander
显示剩余2条评论
5个回答

23
简单的原因是,当添加int.TryParse到语言中时,Nullable<T>不存在。
Eric Lippert的博客文章中,有一行话在底部:
“解决方案是编写自己的扩展方法版本的TryParse,如果在最初实现中可用可空值类型,那么它将被编写。”
这表明在最初实现TryParse时无法使用可空类型。 Eric Lippert曾经参与编写C#编译器的团队,所以我认为他是一个非常权威的来源。

1
感谢您抽出时间查找这个内容 ;) - krimog
4
再次,Stackoverflow上的Erik Lippert粉丝团又开始发威了,只要提到Eric Lippert就能瞬间获得赞啦。 :D - Rand Random
2
还有谁能在不回答问题的情况下回答问题呢? - Magus

21
我不知道确切的原因,但我看到有三个可能的原因:
1)可空类型是在.NET 2.0中引入的,而第一个TryParse方法已经存在于.NET 1.1中。因此,在可空类型被引入时,进行API更改为时已晚;并且新类不会以不同的方式实现TryParse,因为模式已经被确定。
2) 不是所有类型都可以与Nullable结构一起使用,只有值类型可以。但是,有些方法遵循Try*模式,必须返回引用类型。例如,字典完全可以包含null作为项,因此其TryGetValue方法需要一种额外的方式来表示未找到键。
3) Try*方法的编写方式使得可以编写如下代码:
int myValue;
if (int.TryParse("42", out myValue)) {
    // do something with myValue
}
    // do something else
}

现在,想象一下如果TryParse只返回一个int?。你可以弃用myValue变量并失去结果:
if (int.TryParse("42").HasValue) {
    // do something with ... what? You didn't store the conversion result!
}
    // do something else
}

或者您可以添加一个可空变量:

int? myValue = int.TryParse("42");
if (myValue.HasValue) {
    // do something with myValue.Value
}
    // do something else
}

这不再是对当前版本的优势,相反地,它要求在以后的某些情况下编写 myValue.Value,而原本只需要一个简单的 value 就足够了。请注意,在许多情况下,您仅需要有关操作是否成功的信息来用于 if 语句。

  1. 我不知道 Framework 1.0 中存在 TryParse 方法。
  2. 我没有想到那个。
  3. 行数并不是问题。只是我觉得在 C# 中使用 out 关键字有点不自然。
- krimog
@krimog:抱歉,我将1.0更正为1.1,因为我现在找不到1.0的示例(但显然陈述的结论仍然相同)。 - O. R. Mapper
2
为什么使用编程语言的关键字会被认为是“不自然”的? - asawyer
@asawyer:嗯,我不太喜欢方法改变我的引用。当然这是一种语言关键字(否则我们就无法做到这一点,也不会谈论它)。但即使有时它确实可以帮助,我仍然更喜欢返回新值。只是我的个人观点。 - krimog
@krimog 我也更喜欢不可变方法,但是 out 也有它的用处。甚至 goto 偶尔也有它的用途。 - asawyer

5

以下是Julie Lerman(2004年)博客上的一句话:

I have played with nullable in the March preview bits, but not yet in the May and disappointed with the current (but slated for serious improvement by the bcl team!!!) performance when I compared the using nullable<t> over current options. So for example with value types:

comparing myNullableInt.HasValue to (in VB) is myInt < 0

or with reference types

comparing myNullableThing.HasValue to “if not myThing=null

the nullable type is currently much much slower. I have been promised by a few on the BCL team that the plan is to make the nullable MUCH more performant.

I have also been given the hint that in the future, the following will be possible:

Nullable<T> Parse(string value); 
Nullable<Int32> i = Int32.Parse( some String );

And will be more performant than TryParse. So that, too will be interesting.

我假设像往常一样,收益大于成本。
无论如何,在即将推出的C# vNext版本中,您可以执行以下操作:
DateTime.TryParse(s, out var parsedDateTime);

将TryParse转化为一行代码。

2
如果您不需要可空值,甚至现在可以使用一行代码:DateTime dt = DateTime.TryParse(s, out dt) ? dt : DateTime.MinValue; - Tim Schmelter
@TimSchmelter没错,但是有点长了 :) - Yuval Itzchakov

4

还有一个可能的原因:

.NET和C#的泛型几乎没有成型:它与Whidbey(Visual Studio 2005)的特性几乎没有进展。那时更重要的是像在数据库上运行CLR代码这样的功能。

...

最终,由于CLR团队不会追求一个内置VM的泛型设计而没有外部帮助,因此采用了Java一样的擦除模型。

来源:http://blogs.msdn.com/b/dsyme/archive/2011/03/15/net-c-generics-history-some-photos-from-feb-1999.aspx

我的观点是:BCL中的大多数更改(或者至少直接与泛型无关的更改)可能需要同时使用和不使用泛型,以防该特性在最终RTM版本中被削减。

当然,从调用客户端的角度来看,这也是有意义的:所有消费语言(好吧,那时候没有那么多)都理想地能够使用它们 - 而且“out”参数并不像泛型那样先进。


3

关于原因我们只能猜测,但一些可能的原因是:

赋值开销:装箱值会产生一些(小)性能开销,而内置类型则没有。

没有真正的收益:

int res;
if int.TryParse("one", out res) {
  //something
}

与...相比并不差很多

int? res = int.TryParse("one");
if (res.HasValue){
  int realres = res.Value
  //something
}

5
这并不是“盒装”的意思,至少在.NET中的通常意义上不是;一个int?需要8个字节的栈空间;一个int和一个bool:需要8个字节的栈空间...但是:一个bool和一个ref int实际上可能需要12个字节的栈空间(在x64中);p 意思是:out版本实际上需要更多的赋值操作,当然还有一些额外的解引用操作,在另一个版本中是不必要的。我的观点是:两者都有代价。 - Marc Gravell
@MarcGravell:out 不是将值装箱吗? - krimog
1
@krimog 不会,它只是传递字段/本地变量的地址 - 这可以是堆栈上的地址。它明确不创建一个盒子。 - Marc Gravell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接