C# 有比这更好的选择来“打开类型”吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/298976/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a better alternative than this to 'switch on type'?
提问by xyz
Seeing as C# can't switch
on a Type (which I gather wasn't added as a special case because is
relationships mean that more than one distinct case
might apply), is there a better way to simulate switching on type other than this?
看到 C# 不能switch
在类型上(我收集的不是作为特殊情况添加的,因为is
关系意味着case
可能适用多个不同的类型),有没有更好的方法来模拟除此之外的类型的切换?
void Foo(object o)
{
if (o is A)
{
((A)o).Hop();
}
else if (o is B)
{
((B)o).Skip();
}
else
{
throw new ArgumentException("Unexpected type: " + o.GetType());
}
}
采纳答案by JaredPar
Switching on types is definitely lacking in C# (UPDATE: in C#7 / VS 2017 switching on types is supported - see Zachary Yates's answer below). In order to do this without a large if/else if/else statement, you'll need to work with a different structure. I wrote a blog post awhile back detailing how to build a TypeSwitch structure.
C# 中绝对缺乏切换类型(更新:在 C#7 / VS 2017 中支持切换类型 -请参阅下面的 Zachary Yates 的回答)。为了在没有大的 if/else if/else 语句的情况下执行此操作,您需要使用不同的结构。我写了一篇博文,详细介绍了如何构建 TypeSwitch 结构。
https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types
https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types
Short version: TypeSwitch is designed to prevent redundant casting and give a syntax that is similar to a normal switch/case statement. For example, here is TypeSwitch in action on a standard Windows form event
简短版本:TypeSwitch 旨在防止冗余转换并提供类似于普通 switch/case 语句的语法。例如,这里是 TypeSwitch 在标准 Windows 窗体事件上的作用
TypeSwitch.Do(
sender,
TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));
The code for TypeSwitch is actually pretty small and can easily be put into your project.
TypeSwitch 的代码实际上非常小,可以很容易地放入您的项目中。
static class TypeSwitch {
public class CaseInfo {
public bool IsDefault { get; set; }
public Type Target { get; set; }
public Action<object> Action { get; set; }
}
public static void Do(object source, params CaseInfo[] cases) {
var type = source.GetType();
foreach (var entry in cases) {
if (entry.IsDefault || entry.Target.IsAssignableFrom(type)) {
entry.Action(source);
break;
}
}
}
public static CaseInfo Case<T>(Action action) {
return new CaseInfo() {
Action = x => action(),
Target = typeof(T)
};
}
public static CaseInfo Case<T>(Action<T> action) {
return new CaseInfo() {
Action = (x) => action((T)x),
Target = typeof(T)
};
}
public static CaseInfo Default(Action action) {
return new CaseInfo() {
Action = x => action(),
IsDefault = true
};
}
}
回答by Jon Skeet
One option is to have a dictionary from Type
to Action
(or some other delegate). Look up the action based on the type, and then execute it. I've used this for factories before now.
一种选择是使用Type
to Action
(或其他一些委托)的字典。根据类型查找动作,然后执行。我以前在工厂使用过这个。
回答by Pablo Fernandez
Create a superclass (S) and make A and B inherit from it. Then declare an abstract method on S that every subclass needs to implement.
创建一个超类 (S) 并使 A 和 B 继承它。然后在 S 上声明一个每个子类都需要实现的抽象方法。
Doing this the "foo" method can also change its signature to Foo(S o), making it type safe, and you don't need to throw that ugly exception.
这样做“foo”方法还可以将其签名更改为 Foo(S o),使其类型安全,并且您不需要抛出那个丑陋的异常。
回答by Zachary Yates
With C# 7, which shipped with Visual Studio 2017 (Release 15.*), you are able to use Types in case
statements (pattern matching):
使用Visual Studio 2017(版本 15.*)附带的C# 7,您可以在case
语句中使用类型(模式匹配):
switch(shape)
{
case Circle c:
WriteLine($"circle with radius {c.Radius}");
break;
case Rectangle s when (s.Length == s.Height):
WriteLine($"{s.Length} x {s.Height} square");
break;
case Rectangle r:
WriteLine($"{r.Length} x {r.Height} rectangle");
break;
default:
WriteLine("<unknown shape>");
break;
case null:
throw new ArgumentNullException(nameof(shape));
}
With C# 6, you can use a switch statement with the nameof() operator(thanks @Joey Adams):
在 C# 6 中,您可以使用带有nameof() 运算符的 switch 语句(感谢 @Joey Adams):
switch(o.GetType().Name) {
case nameof(AType):
break;
case nameof(BType):
break;
}
With C# 5 and earlier, you could use a switch statement, but you'll have to use a magic string containing the type name... which is not particularly refactor friendly (thanks @nukefusion)
使用 C# 5 及更早版本,您可以使用 switch 语句,但您必须使用包含类型名称的魔法字符串......这不是特别适合重构(感谢@nukefusion)
switch(o.GetType().Name) {
case "AType":
break;
}
回答by Jonas Kongslund
回答by Hallgrim
I such cases I usually end up with a list of predicates and actions. Something along these lines:
在这种情况下,我通常会得到一个谓词和动作列表。沿着这些路线的东西:
class Mine {
static List<Func<object, bool>> predicates;
static List<Action<object>> actions;
static Mine() {
AddAction<A>(o => o.Hop());
AddAction<B>(o => o.Skip());
}
static void AddAction<T>(Action<T> action) {
predicates.Add(o => o is T);
actions.Add(o => action((T)o);
}
static void RunAction(object o) {
for (int i=0; o < predicates.Count; i++) {
if (predicates[i](o)) {
actions[i](o);
break;
}
}
}
void Foo(object o) {
RunAction(o);
}
}
回答by sep332
You should really be overloading your method, not trying to do the disambiguation yourself. Most of the answers so far don't take future subclasses into account, which may lead to really terrible maintenance issues later on.
你真的应该重载你的方法,而不是试图自己消除歧义。到目前为止,大多数答案都没有考虑未来的子类,这可能会导致以后出现非常糟糕的维护问题。
回答by Marc Gravell
回答by plinth
I agree with Jon about having a hash of actions to class name. If you keep your pattern, you might want to consider using the "as" construct instead:
我同意 Jon 关于对类名进行操作的散列。如果您保留您的模式,您可能需要考虑使用“as”结构:
A a = o as A;
if (a != null) {
a.Hop();
return;
}
B b = o as B;
if (b != null) {
b.Skip();
return;
}
throw new ArgumentException("...");
The difference is that when you use the patter if (foo is Bar) { ((Bar)foo).Action(); } you're doing the type casting twice. Now maybe the compiler will optimize and only do that work once - but I wouldn't count on it.
不同的是,当你使用模式 if (foo is Bar) { ((Bar)foo).Action(); 你正在做两次类型转换。现在也许编译器会优化并且只执行一次 - 但我不会指望它。
回答by Sunny Milenov
Create an interface IFooable
, then make your A
and B
classes to implement a common method, which in turn calls the corresponding method you want:
创建一个接口IFooable
,然后让你A
和B
类实现一个通用的方法,依次调用你想要的对应方法:
interface IFooable
{
public void Foo();
}
class A : IFooable
{
//other methods ...
public void Foo()
{
this.Hop();
}
}
class B : IFooable
{
//other methods ...
public void Foo()
{
this.Skip();
}
}
class ProcessingClass
{
public void Foo(object o)
{
if (o == null)
throw new NullRefferenceException("Null reference", "o");
IFooable f = o as IFooable;
if (f != null)
{
f.Foo();
}
else
{
throw new ArgumentException("Unexpected type: " + o.GetType());
}
}
}
Note, that it's better to use as
instead first checking with is
and then casting, as that way you make 2 casts, so it's more expensive.
请注意,最好使用as
而不是先检查is
然后进行转换,因为这样您进行了 2 次转换,因此成本更高。