如何在C#中进行静态转换?

3

假设有以下几种类型:

interface I {}
class C : I {}

如何进行静态类型转换?我的意思是:如何以一种在编译时检查其类型的方式改变它的类型?

在C++中,您可以这样做:static_cast<I*>(c)。在C#中,我能做的最好方法是创建一个替代类型的临时变量并尝试分配它:

var c = new C();
I i = c;  // statically checked

但是这会影响编程的流畅性。我必须创建一个新变量来进行类型检查。因此,我选择了类似于以下方式:

class C : I
{
    public I I { get { return this; } }
}

现在我可以通过调用c.I来将C静态转换为I。

在C#中有更好的方法吗?

(如果有人想知道我为什么要这样做,那是因为我使用显式接口实现,从另一个成员函数内调用其中一个需要先将其转换为接口类型,否则编译器无法找到该方法。)

更新

我想到的另一种选择是对象扩展:

public static class ObjectExtensions
{
    [DebuggerStepThrough]
    public static T StaticTo<T>(this T o)
    {
        return o;
    }
}

因此,((I)c).Doit() 也可以写成 c.StaticTo<I>().Doit()。嗯...可能还是会坚持使用简单的强制类型转换。不过我想把这个其他选项也发布出来。

4个回答

6
只需简单地进行转换:
(I)c

编辑示例:

var c = new C();

((I)c).MethodOnI();

(面掌)...好吧,这很显然,为什么我没试过呢?难看但是能用。谢谢 :) - scobi
2
现在我再想一想,编译器会跳过静态类型检查的一个小技巧。假设你从一个对象开始。它可以被转换成任何东西而没有错误,而上面我刚刚添加到问题中的扩展方法StaticTo<T>将捕获这样的错误转换。这只是一个小细节。 - scobi

2
编写一个扩展方法,使用您在更新中提到的技巧:
public static class ObjectExtensions
{
    public static T StaticCast<T>(this T o) => o;
}

使用方法:

things.StaticCast<IEnumerable>().GetEnumerator();

如果thingsIEnumerable<object>,这段代码可以编译通过。如果thingsobject,则编译失败。
// Compiles (because IEnumerable<char> is known at compiletime
// to be IEnumerable too).
"adsf".StaticCast<IEnumerable>().GetEnumerator();

// error CS1929: 'object' does not contain a definition for 'StaticCast'
// and the best extension method overload
// 'ObjectExtensions.StaticCast<IEnumerable>(IEnumerable)'
// requires a receiver of type 'IEnumerable'
new object().StaticCast<IEnumerable>().GetEnumerator();

为什么要使用static_cast?

重构期间的常见做法是先进行更改,然后验证更改是否引起回归。可以通过各种方式和在不同阶段检测到回归。例如,某些类型的重构可能导致API更改/破坏,并需要重构代码库的其他部分。

如果代码中的一部分希望收到一个在编译时应知道实现接口(IInterfaceA)的类型(ClassA),并且该代码想要直接访问接口成员,则可能必须向下转换为接口类型,例如访问显式实现的接口成员。如果在重构之后,ClassA不再实现IIterfaceA,则您将根据向下转换到接口的方式而得到不同类型的错误:

  1. C风格的强制类型转换:((IInterfaceA)MethodReturningClassA()).Act(); 将突然变成运行时转换并抛出运行时错误。
  2. 赋值给显式类型变量:IInterfaceA a = MethodReturningClassA(); a.Act(); 将引发编译时错误。
  3. 使用类似于 static_cast<T> 的扩展方法:MethodReturningClassA().StaticCast<IInterfaceA>().Act(); 将引发编译时错误。

如果您希望转换为下转并在编译时进行验证,则应使用强制进行编译时验证的转换方法。这使代码原始开发人员编写类型安全代码的意图变得清晰。编写类型安全的代码有一个好处,即更易于在编译时进行验证。通过进行一些工作来澄清您对类型安全性的选择意图,向其他开发人员,自己和编译器都表明了您的意图,在验证代码时,您会神奇地获得编译器的帮助,并可以比后期(例如运行时崩溃,如果代码没有完整的测试覆盖)更早地捕获重构的影响(在编译时)。


2
var c = new C(); 
I i = c;  // statically checked

等于

I i = new C();

正确。不幸的是,那并没有回答我的问题。 - scobi

1

如果你只是想查看一个对象是否实现了特定类型,那么你应该使用as

I i = whatever as i;
if (i == null) // It wasn't

否则,你只需进行强制转换。(在.NET中并没有像C++中那样多种类型的转换方式——除非你需要深入了解,但那时更多关于WeakReference和类似的东西。)
I i = (I)c;

如果你只是想要一种方便的方法将任何实现了I的东西转换成一个I,那么你可以使用扩展方法或类似的东西。

public static I ToI(this I @this)
{
    return @this;
}

请注意,as关键字适用于类,但是对于诸如bool之类的基本类型不起作用,因此,如果您需要将例如int强制转换为bool,则必须使用丑陋的C样式转换,在C ++中现在强烈不建议使用(这让可怜的OP显然没有预料到C#这么新的语言)或者您可以采用上面显示的第二种方法,或者使用OP更新提供的整洁模板化方法。 - Seldom 'Where's Monica' Needy
as 有时会执行动态转换而不抛出编译时错误。你的答案在结尾隐藏了真正的宝石;-) - binki
@binki:as被设计为运行时命令。在最合适使用as的场景中,您不希望出现编译时错误。 - John Fisher
这个问题似乎是想问如何将一个对象转换为基类/接口,以一种可以在编译时进行验证的方式。因为 as 会在编译时和运行时之间静默切换类型检查,所以它不像 C++ 的 static_cast<T> 那样安全地强制进行编译时检查。如果您从未打算进行需要运行时验证的转换,那么您是否更希望出现编译时错误? - binki
如果你对问题的假设是正确的,那么被接受的答案就完全错了。类型转换也不能提供完整的编译时检查。 - John Fisher
@JohnFisher 引用问题:“我的意思是:我如何更改其类型,以便在编译时进行检查?” Danny Chenn的答案和你的答案结尾都解决了这个问题。也许我们应该设法更改被接受的答案? - binki

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