C#中等价于VB.NET的DirectCast

53
C#有没有类似于VB.NET的DirectCast的等效方法? 我知道C#中有()转换和'as'关键字,但是它们对应于CType和TryCast。 明确一下,这些关键字的作用如下: CType/()转换:如果它已经是正确类型,则将其转换,否则查找类型转换器并调用它。如果找不到类型转换器,则抛出InvalidCastException。 TryCast/"as"关键字:如果它是正确的类型,则将其转换,否则返回null。 DirectCast:如果它是正确的类型,则将其转换,否则抛出InvalidCastException。 尽管我已经解释了上述内容,但仍有人回答说()是等效的,因此我将进一步扩展说明为什么这不是正确的。 DirectCast只允许在继承树上进行收缩或扩展转换。它不支持像()那样跨不同分支的转换,例如: C# - 这个编译并运行:
//This code uses a type converter to go across an inheritance tree
double d = 10;
int i = (int)d;

VB.NET - 这段代码无法编译

'Direct cast can only go up or down a branch, never across to a different one.
Dim d As Double = 10
Dim i As Integer = DirectCast(d, Integer)

在VB.NET中与我的C#代码等效的是CType:

'This compiles and runs
Dim d As Double = 10
Dim i As Integer = CType(d, Integer)

11
+1 表示赞同定义关键词,不让人们自行查找。 - Greg
4
在C#中,“string s =“10”;int i =(int)s;”不能编译。 - Robert Davis
3
你对关键字的作用存在误解。虽然像你示例代码中的 int i = (int)d; 可以执行已知类型的已知转换,但在任何情况下,如果你实际上在VB.NET中使用 DirectCast(即当对象类型在编译时未知时),使用 () 进行转换在C#中并不会执行转换--它只是进行强制类型转换。因此,装箱后的双精度浮点数无法作为整数拆箱。基于这个原因,这两种写法是等效的。 - Dan Tao
1
出于好奇,你为什么需要这个? - RCIX
2
你关于 ()DirectCast 的区别只适用于 值类型 是正确的,但对于 引用类型 是错误的。[对于引用类型,这两者是等效的;不执行任何转换。] 这使得这个比较毫无意义,因为在我看来,没有理由使用 DirectCast 来处理 值类型。我的看法是:对于值类型,() 等同于 CType,而对于引用类型,则等同于 DirectCast - ToolmakerSteve
显示剩余5条评论
11个回答

14

看起来你想要的功能不在C#中。不过可以尝试以下方法...

static T DirectCast<T>(object o, Type type) where T : class
{
    if (!(type.IsInstanceOfType(o)))
    {
        throw new ArgumentException();
    }
    T value = o as T;
    if (value == null && o != null)
    {
        throw new InvalidCastException();
    }
    return value;
}

或者,即使它与VB不同,也可以像这样调用:

static T DirectCast<T>(object o) where T : class
{
    T value = o as T;
    if (value == null && o != null)
    {
        throw new InvalidCastException();
    }
    return value;
}

2
如果您使用is而不是as,则可以删除T:class的要求。static T DirectCast<T>(object o) { if(o != null && o is T) return (T)o; else throw new InvalidCastException(); } - Robert Davis
3
在您的代码中,您没有使用“type”参数,因此可以将其省略。或者可以在额外的检查中使用它:如果(typeof(T)!= type),则会抛出InvalidArgumentException(...)。 - Hans Kesting
2
@Robert Davis:实际上,对o进行类型检查是完全不必要的,因为(T)o如果类型不匹配,会自动抛出InvalidCastException异常。考虑到这一点,真正等效的代码只需写成return (T)o;,这样更清楚地表明该方法本质上就是(T)(object)value的等价形式。请看我的答案。 - Dan Tao
1
@Dan Tao:你说得对。由于类型无法从对象创建隐式/显式运算符,(T)(object)value将永远不会调用任何转换函数。 - Robert Davis
2
我不认为type is T的检查会做你想要的事情。实际上,它是相当错误的。你可能在寻找type.IsInstanceOfType(o)。将isas结合使用也是多余的。 - Aaronaught
显示剩余8条评论

8

第二次更新::

好的,这里有一个C#方法,据说可以基本上做到DirectCast在VB.NET中所做的事情。

static T DirectCast<T>(object o) where T : class
{
    T value = o as T;
    if (value == null && o != null)
    {
        throw new InvalidCastException();
    }
    return value;
}

以下是上述方法存在的问题:
  1. 它有一个“where T:class”约束,而DirectCast没有。
  2. 它将其参数作为System.Object进行装箱——同样,并非DirectCast的特点(至少我不知道)。
  3. 它不必要地使用了as(这就是为什么它首先有一个class约束);如果不起作用,则调用(T)o将抛出InvalidCastException。为什么要使用as检查值是否匹配,只会抛出与一开始使用(T)o路线相同的异常呢?
该方法可以重写为以下形式,以提供与DirectCast相同的结果:
static T DirectCast<T>(object o) {
    return (T)o;
}

有趣的观察:这个方法实际上只是将一个值装箱然后尝试取消装箱。换句话说,DirectCast<int>(12.0)实际上与(int)(object)12.0相同(两者都会抛出异常)。认识到这一点,就可以发现提议中的DirectCast<T>方法完全没有必要。
现在,这里有一个示例,展示了VB.NET和C#之间DirectCast和使用()进行转换的“不同”之处:
VB:
Dim i As Integer = 12
Dim l As Long = DirectCast(i, Long) ' does not compile '

C#:

int i = 12;
long l = i; // DOES compile

好的,一个编译,另一个不编译。但是看看那段代码。当您已经知道对象的类型时,DirectCast有什么意义呢?这不是一个现实的比较,因为在VB.NET中,没有任何理由像上面的代码一样调用DirectCast。(如果您想将一个已知为类型System.Int32的值转换为类型System.Int64的值,在VB.NET中,您应该使用CLng,而不是DirectCast。)如果在那里有一个变量类型为System.Object那么使用DirectCast就有意义了,下面的代码确实等效:

VB:

Dim i As Integer = 12
Dim o As Object = i
Dim l As Long = DirectCast(o, Long) ' compiles, throws an exception '

C#:

int i = 12;
object o = i;
long l = (long)o; // compiles, throws an exception

因此,我认为在VB.NET中,任何情况下如果使用DirectCast是有意义的(即对象的类型在编译时未知),则它与C#中直接使用()样式转换是相同的。

编辑: 我真是太不好了,发了一些无法编译的VB代码。经过重新考虑我的话,我撤回了第二个答案,但仍坚持第一个答案。

如果您指的是使用DirectCast将未知类型的对象转换为所需类型,则它等同于C#中的()转换:

VB:

Dim o As Object = SomeObject()
Dim i As Integer = DirectCast(o, Integer)

C#:

object o = SomeObject();
int i = (int)o;

这是因为,如果将o类型定义为System.Object,那么在C#中执行()操作时,程序会尝试对其进行拆箱操作。如果类型不完全匹配,则会失败;例如,如果o是一个装箱的System.Double,则(int)o将抛出异常,因为在将其转换为System.Int32之前,o必须作为System.Double进行拆箱操作(如果你不相信,请自行尝试!)。

注意:以下内容不准确,因为DirectCast不执行宽化转换;无论如何,我还是保留下来。

另一方面,在处理宽化和缩窄转换时,使用C#中的()操作比简单的强制转换要多做更多的工作,正如你所指出的那样(即,你可以执行(int)someDouble)。在这种情况下,DirectCast等效于C#中的普通赋值:

VB:

Dim i As Integer = 12
Dim l As Long = DirectCast(i, Long) ' does not compile, actually '

C#:

int i = 12;
long l = i;

你的VB代码无法编译。我猜你在directcast的类型参数中使用了12,而实际上你可能想用long,但这也无法编译。同时确保开启了Option Strict选项,否则你会得到一堆隐式转换(VB编译器喜欢读心术)。 - csauve
@csauve:好主意。我真的很愚蠢!无论如何,请看一下我的第一个观点。我确实相信,当在类型为“System.Object”的对象上使用时,“DirectCast”与C#的“()”操作实际上是相同的。 - Dan Tao
顺便提一下,关于我最初发布的代码中的“Option Strict”......,“Option Infer”同样适用。(无论如何,我已经添加了类型,以确保完整性。) - Dan Tao

1

你可以自己实现:

static T CastTo<T>(this object obj) { return (T)obj; }

使用方法如下:

3.5.CastTo<int>(); //throws InvalidCastException.

这个方法可行且不涉及用户定义的转换器,因为泛型在运行时被“解析”,但类型转换是在编译时解决的 - 框架实际上并没有为每个T生成不同的实现,而是共享类似T的实现,因此运行时没有信息来解决自定义转换。


1

实际上,编译器只会在推断出类型变量无法转换为其他类型时才会捕获 DirectCast 违规。

以下是实际等效项:

double d = 10;
int i = (int)d;

Dim d As Double = 10
Dim i As Integer = d

请注意此结构的危险性。当您仅在VB.NET中将double分配给整数时,double将意外地缩小为整数。
而C#程序员在.NET中获得了编译时安全性,不会意外缩小变量。 VB.NET程序员必须始终使用DirectCast作为安全编程习惯。
这些是实际等效项:
// Will not compile, cannot convert double to int

double d = 10;
int i = d;

' Will not compile, cannot convert double to int

Dim d As Double = 10
Dim i As Integer = DirectCast(d, Integer)

关于丹·陶的评论

C#中没有必要使用DirectCast。运行时也会防止将long类型转换为整数类型的值。这就是OP所争论的,即C#没有DirectCast,而DirectCast可以防止分配不同类型的变量,而“由于”C#没有这个DirectCast,它将在分配不同类型时引发静默错误。但正如您所看到的,事实并非如此。C#的强制转换与DirectCast完全相同。这将导致InvalidCastException运行时错误:

long l = 10;
object o = l;
int i = (int)o;

这也会导致与上述相同的运行时错误

Dim l As Long = 10
Dim o As Object = l
Dim i As Integer = DirectCast(o, Integer)

现在,这就是“有趣”的部分。使用VB.NET,您必须记住许多关键字才能完成某些操作。在C#中,如果一个给定的关键字可以在另一种情况下使用(比如在这个变量向下转换的例子中),他们不会发明另一个关键字来实现它。

在C#中,您只需要这样做:

long l = 10;
object o = l;
int i = (int)(long)o;

在VB.NET中,如果你真的想下转换变量,并且想要正交的方式来做到这一点,即只记住一个关键字,你必须这样做:
Dim l As Long = 10
Dim o As Object = l
Dim i As Integer = DirectCast(DirectCast(o, Long), Integer)

但是这样不会编译,那么我们如何将 long 强制转换为 integer 呢?你必须记住 VB.NET 的其他关键字。而在 C# 中,它是正交的,你可以使用这个结构 (typehere) 来取消装箱变量,你也可以使用相同的结构 (typehere) 进行向下/向上转换。在 VB.NET 中,从对象中加载值并进行向下转换之间存在根本性的断开。因此,在 VB.NET 中,你必须这样做:

Dim l As Long = 10
Dim o As Object = l
Dim i As Integer = CType(o, Integer)

嗯.. 我认为 OP 的困惑源于 C# 多次使用 (typehere)。首先,它用于向下转型;其次,同样的结构(请查看此帖子的第一部分,object o = l)也用于从对象中取消封箱的值,这个行为具有 DirectCast 的安全类型转换行为。它们是相同的!
这种向下转型...
long l = 1;
int i = (int) l;

...不等同于:

Dim l As Long = 1
Dim i As Integer = DirectCast(l, Integer)

如果你想进行向下转型,你需要这样做:
Dim l As Long = 1
Dim i As Integer = CInt(l) ' Can also use CType

现在,如果一个 VB.NET 程序员是有意识地编程,而不是在编码时昏昏欲睡,那么他/她为什么会使用 DirectCast 呢?因为他/她完全知道它不能分配不同的类型。如果这个 VB.NET 程序员真正想要的是向下转换,他/她就不应该首先尝试使用 DirectCast。现在,当这个 VB.NET 程序员发现 DirectCast 不能用于向下转换时,必须回退他/她所写的内容,并将其替换为 CInt(或 CType)。

1
尝试打开Option Strict -- 我希望大多数的商店都会强制要求任何VB开发人员在编写代码时打开它。 - csauve
@Michael:这也是我想的,但正如csauve在我的答案评论中指出的那样,你答案末尾的代码示例实际上并不等同。在C#中,可以将long赋值给int,但VB.NET不允许使用DirectCastLong赋值给Integer - Dan Tao
@Dan Tao - 请看我上面的评论。 - Michael Buen
我们的答案有些趋同了 ;) 但我的观点是特别针对你的代码 double d = 10; int i = d; -- 你正确地评论说它不会编译,就像 DirectCast(d, Integer) 不会编译一样;另一方面,在 C# 中,int i = 10; long l = i; 编译通过,但 DirectCast(i, Long) 不会。除此之外,我认为我们完全在同一个页面上。 - Dan Tao
@Dan Tao:关于DirectCast(i, Long)。我认为VB.NET语言设计者不想让他们的程序员受苦。为什么他们不允许VB.NET程序员使用这个(他们允许)?--> Dim l As Long = i。总的来说,我认为DirectCast只是当程序员错误地使用它们(因为错误的使用预期)时,应该被删除,当他们真正想要的是CInt/CLng、CType等。 - Michael Buen

1

VB.NET:

Dim xxx as label = Directcast(sender, label)

C#:

label xxx = (label)sender;

3
欢迎来到 StackOverflow。请解释一下你的代码,并说明这段代码如何解决问题。 - Mehdi Yeganeh
1
当你在回复一个8年前的问题时,最好仔细阅读完整个问题以及已有的答案。 - csauve

0

在C#中有两种类型的转换。如果没有额外的代码,就没有与DirectCast关键字相当的等效物。最接近你可以使用的是()

你有:

My_Object c = (My_Object)object

并且

My_Object c = object as My_Object

在第一个例子中,如果强制类型转换失败,它会抛出错误。你的意思是,“我知道这个对象是什么,如果不是,就有问题”。
在第二种情况下,可能会将 null 赋值给 c(不能将 null 赋值给值类型)。在这种情况下,你的意思是,“我认为我知道这是什么,但是如果不是,不要抛出错误,因为可能没有问题。”
另一篇解释强制类型转换的文章:
显式类型转换和隐式类型转换之间的区别是什么?

糟糕,我把术语搞混了。谢谢。 - kemiller2002
1
如上所述,()转换允许类型转换器,而DirectCast不允许。 - csauve

0

你真的尝试运行你的示例代码了吗?

关于...

//This code uses a type converter to go across an inheritance tree
string s = "10";
int i = (int)s;

...你假设它会运行。但实际上它并没有运行。


我已经将其编辑为使用双精度浮点数。我曾经错误地认为在C#中string->int是合法的。double->int应该可以工作。 - csauve

0

让我试着来解释一下。

首先,让我明确一点。这个不会编译:

//This code uses a type converter to go across an inheritance tree
string s = "10";
int i = (int)s;

VB的CType

在VB中,您可以使用:

Dim s as String = "10"
Dim i as Integer = CType(s, Integer)

在C#中,我会使用:
string s = "10";
int i = Convert.ToInt32(s);

VB 的 DirectCast

如果它是正确的类型,就进行强制转换, 否则抛出 InvalidCastException 异常。

直接转换只能在同一分支内上下转换,不能跨越到不同的分支。

从这个解释来看,它应该是 C# 转换的直接等价物。然而,在 C# 中,你只需要为向下转换指定转换运算符即可。向上转换完全是可选的。例如:

// casting down
object t = "some random string needing to be casted down";
string s = (string) t;
// casting up
object a = s;
// explicitly casting up although it's totally optional
object b = (object) s;

C#的类型转换不会寻找任何类型转换器。它只会寻找您尝试转换到的类型所定义的显式/隐式操作符重载。


VB 的 TryCast

你已经正确理解了,这与 C# 中的关键字等效。


C#()中的强制转换允许类型转换器。DirectCast则不行。将字符串替换为double,您将看到区别。C#允许使用()强制转换进行double->int,而vb.net只能使用ctype进行转换。 - csauve
() 强制转换不使用类型转换器。它作用于 IConvertible 接口。您可以自行查看 MSDN 的 TypeConverter 页面:http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverter.aspx。没有一个示例显示您能够使用 C# 强制转换来触发使用 TypeConverters。 - Amry
你是正确的,我错了,“C#强制转换不会寻找任何类型转换器。它只会寻找您尝试转换到的类型的任何定义的显式/隐式运算符重载。”这使它成为一种独特的东西 - 它既不完全是ctype,也不完全是directcast。 - csauve

0

DirectCast()不总是生成相同的CIL,因此我认为这是VB和C#编译器之间的差异。

据我所知,引用类型使用castclass CIL指令进行转换,而对于值类型,编译器根据输入类型生成相关的CIL。

在C#中,从doubleinteger的转换会发出conv.i4 CIL指令,如果值过大,则会愉快地覆盖输出中的符号位或其他内容。在Visual Basic中,这是一个编译错误。

有趣的是,如果您使用一个中间的object变量来保存double,那么转换将在C#和Visual Basic中都失败...但在运行时。两个编译器都会发出一个unbox指令,而不是尝试进行转换。


-1

强制转换 () 应该相同; 它会抛出 InvalidCastException。在 C# 中尝试一下:

 string t = "hello";
 object x = t;
 int j = (int) x;

是的,但是"(int) x"实际上是试图将一个字符串转换为整数,因此如果t = "10",它会成功。DirectCast不允许类型转换器,因此即使t = "10",它仍然会失败。 - csauve
@Collin Sauve:我不确定你从哪里得到这个想法,但是它是错误的。如果您尝试将t =“10”转换为int j =(int)t,则代码根本无法编译。 - Amry
抱歉,字符串只是一个糟糕的例子。double->int是更好的例子。double d = 10; int i = (int)d; 在C#中这很好,但如果我用directcast(d, integer)替换(int)d,在VB.NET中它就不起作用。 - csauve
我认为Collin Sauve并没有试图从VB.NET运行C#的非对应版本。如果t =“10”(字符串),上述代码也会导致InvalidCastException。@CollinSauve:在做出假设之前,请在Visual Studio中运行代码。 - Michael Buen

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