在C#上看不到切换(我收集的内容并未作为特殊情况添加,因为是 - 关系意味着多个不同的案例可能适用),是否有更好的方法来模拟切换类型?
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());
}
}
答案 0 :(得分:268)
C#中肯定缺少开启类型(更新:在C#7 / VS 2017中,支持类型切换 - see Zachary Yates's answer below )。为了在没有大的if / else if / else语句的情况下执行此操作,您需要使用不同的结构。我写了一篇博客文章,详细介绍了如何构建TypeSwitch结构。
http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx
简短版本: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"));
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
};
}
}
答案 1 :(得分:232)
With 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));
}
使用C#6,您可以使用nameof() operator的开关语句(感谢@Joey Adams):
switch(o.GetType().Name) {
case nameof(AType):
break;
case nameof(BType):
break;
}
使用C#5及更早版本,您可以使用switch语句,但是您必须使用包含类型名称的魔术字符串...这不是特别重构友好的(感谢@nukefusion)
switch(o.GetType().Name) {
case "AType":
break;
}
答案 2 :(得分:100)
一种选择是拥有从Type
到Action
(或其他一些代表)的字典。根据类型查找操作,然后执行它。我之前已经将它用于工厂了。
答案 3 :(得分:49)
在我的脑海中JaredPar's answer,I wrote他的TypeSwitch
类的变体,它使用类型推断来获得更好的语法:
class A { string Name { get; } }
class B : A { string LongName { get; } }
class C : A { string FullName { get; } }
class X { public string ToString(IFormatProvider provider); }
class Y { public string GetIdentifier(); }
public string GetName(object value)
{
string name = null;
TypeSwitch.On(value)
.Case((C x) => name = x.FullName)
.Case((B x) => name = x.LongName)
.Case((A x) => name = x.Name)
.Case((X x) => name = x.ToString(CultureInfo.CurrentCulture))
.Case((Y x) => name = x.GetIdentifier())
.Default((x) => name = x.ToString());
return name;
}
请注意Case()
方法的顺序非常重要。
Get the full and commented code for my TypeSwitch
class。这是一个有效的缩写版本:
public static class TypeSwitch
{
public static Switch<TSource> On<TSource>(TSource value)
{
return new Switch<TSource>(value);
}
public sealed class Switch<TSource>
{
private readonly TSource value;
private bool handled = false;
internal Switch(TSource value)
{
this.value = value;
}
public Switch<TSource> Case<TTarget>(Action<TTarget> action)
where TTarget : TSource
{
if (!this.handled && this.value is TTarget)
{
action((TTarget) this.value);
this.handled = true;
}
return this;
}
public void Default(Action<TSource> action)
{
if (!this.handled)
action(this.value);
}
}
}
答案 4 :(得分:14)
创建一个超类(S)并使A和B继承它。然后在每个子类需要实现的S上声明一个抽象方法。
这样做“foo”方法也可以将其签名更改为Foo(S o),使其类型安全,并且您不需要抛出那个丑陋的异常。
答案 5 :(得分:8)
如果您使用的是C#4,则可以使用新的动态功能来实现一个有趣的替代方案。我并不是说这更好,事实上它似乎很可能会变慢,但它确实有一定的优雅。
class Thing
{
void Foo(A a)
{
a.Hop();
}
void Foo(B b)
{
b.Skip();
}
}
用法:
object aOrB = Get_AOrB();
Thing t = GetThing();
((dynamic)t).Foo(aorB);
这样做的原因是C#4动态方法调用在运行时而不是编译时解析了它的重载。我写了一些关于这个想法的更多信息quite recently。我再次重申,这可能比其他所有建议都要糟糕,我只是将其作为一种好奇心提供。
答案 6 :(得分:7)
你应该真的超载你的方法,而不是试图自己消除歧义。到目前为止,大多数答案都没有考虑到未来的子类,这可能会在以后导致非常糟糕的维护问题。
答案 7 :(得分:7)
对于内置类型,您可以使用TypeCode枚举。请注意,GetType()有点慢,但在大多数情况下可能不相关。
switch (Type.GetTypeCode(someObject.GetType()))
{
case TypeCode.Boolean:
break;
case TypeCode.Byte:
break;
case TypeCode.Char:
break;
}
对于自定义类型,您可以创建自己的枚举,以及具有抽象属性或方法的接口或基类...
属性的抽象类实现
public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
public abstract FooTypes FooType { get; }
}
public class FooFighter : Foo
{
public override FooTypes FooType { get { return FooTypes.FooFighter; } }
}
方法的抽象类实现
public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
public abstract FooTypes GetFooType();
}
public class FooFighter : Foo
{
public override FooTypes GetFooType() { return FooTypes.FooFighter; }
}
属性的接口实现
public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
FooTypes FooType { get; }
}
public class FooFighter : IFooType
{
public FooTypes FooType { get { return FooTypes.FooFighter; } }
}
方法的接口实现
public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
FooTypes GetFooType();
}
public class FooFighter : IFooType
{
public FooTypes GetFooType() { return FooTypes.FooFighter; }
}
我的一位同事也告诉了我这件事:这样做的好处是你可以将它用于任何类型的对象,而不仅仅是你定义的对象。它的缺点是更大更慢。
首先定义一个这样的静态类:
public static class TypeEnumerator
{
public class TypeEnumeratorException : Exception
{
public Type unknownType { get; private set; }
public TypeEnumeratorException(Type unknownType) : base()
{
this.unknownType = unknownType;
}
}
public enum TypeEnumeratorTypes { _int, _string, _Foo, _TcpClient, };
private static Dictionary<Type, TypeEnumeratorTypes> typeDict;
static TypeEnumerator()
{
typeDict = new Dictionary<Type, TypeEnumeratorTypes>();
typeDict[typeof(int)] = TypeEnumeratorTypes._int;
typeDict[typeof(string)] = TypeEnumeratorTypes._string;
typeDict[typeof(Foo)] = TypeEnumeratorTypes._Foo;
typeDict[typeof(System.Net.Sockets.TcpClient)] = TypeEnumeratorTypes._TcpClient;
}
/// <summary>
/// Throws NullReferenceException and TypeEnumeratorException</summary>
/// <exception cref="System.NullReferenceException">NullReferenceException</exception>
/// <exception cref="MyProject.TypeEnumerator.TypeEnumeratorException">TypeEnumeratorException</exception>
public static TypeEnumeratorTypes EnumerateType(object theObject)
{
try
{
return typeDict[theObject.GetType()];
}
catch (KeyNotFoundException)
{
throw new TypeEnumeratorException(theObject.GetType());
}
}
}
然后你可以像这样使用它:
switch (TypeEnumerator.EnumerateType(someObject))
{
case TypeEnumerator.TypeEnumeratorTypes._int:
break;
case TypeEnumerator.TypeEnumeratorTypes._string:
break;
}
答案 8 :(得分:6)
我喜欢Virtlink的use of implicit typing以使交换机更具可读性,但我并不喜欢早期出来是不可能的,而且我们也是如此做分配。让我们稍微调整一下。
public static class TypeSwitch
{
public static void On<TV, T1>(TV value, Action<T1> action1)
where T1 : TV
{
if (value is T1) action1((T1)value);
}
public static void On<TV, T1, T2>(TV value, Action<T1> action1, Action<T2> action2)
where T1 : TV where T2 : TV
{
if (value is T1) action1((T1)value);
else if (value is T2) action2((T2)value);
}
public static void On<TV, T1, T2, T3>(TV value, Action<T1> action1, Action<T2> action2, Action<T3> action3)
where T1 : TV where T2 : TV where T3 : TV
{
if (value is T1) action1((T1)value);
else if (value is T2) action2((T2)value);
else if (value is T3) action3((T3)value);
}
// ... etc.
}
嗯,这让我的手指受伤了。让我们在T4中做到这一点:
<#@ template debug="false" hostSpecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO" #>
<#
string GenWarning = "// THIS FILE IS GENERATED FROM " + Path.GetFileName(Host.TemplateFile) + " - ANY HAND EDITS WILL BE LOST!";
const int MaxCases = 15;
#>
<#=GenWarning#>
using System;
public static class TypeSwitch
{
<# for(int icase = 1; icase <= MaxCases; ++icase) {
var types = string.Join(", ", Enumerable.Range(1, icase).Select(i => "T" + i));
var actions = string.Join(", ", Enumerable.Range(1, icase).Select(i => string.Format("Action<T{0}> action{0}", i)));
var wheres = string.Join(" ", Enumerable.Range(1, icase).Select(i => string.Format("where T{0} : TV", i)));
#>
<#=GenWarning#>
public static void On<TV, <#=types#>>(TV value, <#=actions#>)
<#=wheres#>
{
if (value is T1) action1((T1)value);
<# for(int i = 2; i <= icase; ++i) { #>
else if (value is T<#=i#>) action<#=i#>((T<#=i#>)value);
<#}#>
}
<#}#>
<#=GenWarning#>
}
稍微调整Virtlink的示例:
TypeSwitch.On(operand,
(C x) => name = x.FullName,
(B x) => name = x.LongName,
(A x) => name = x.Name,
(X x) => name = x.ToString(CultureInfo.CurrentCulture),
(Y x) => name = x.GetIdentifier(),
(object x) => name = x.ToString());
可读且快速。现在,由于每个人都在指出他们的答案,并且考虑到这个问题的性质,顺序在类型匹配中很重要。因此:
答案 9 :(得分:5)
鉴于继承有助于将对象识别为多种类型,我认为切换可能导致不明确的歧义。例如:
案例1
{
string s = "a";
if (s is string) Print("Foo");
else if (s is object) Print("Bar");
}
案例2
{
string s = "a";
if (s is object) Print("Foo");
else if (s is string) Print("Bar");
}
因为s是一个字符串和一个对象。
我想当你写一个switch(foo)
时,你希望foo匹配case
个语句中的一个而且只有一个。通过打开类型,编写case语句的顺序可能会改变整个switch语句的结果。我认为这是错误的。
您可以考虑对“typeswitch”语句的类型进行编译器检查,检查枚举类型是否不会相互继承。但这并不存在。
foo is T
与foo.GetType() == typeof(T)
!!
答案 10 :(得分:5)
是的,感谢C#7可以实现,这是如何完成的(使用expression pattern):
switch(o)
{
case A a:
a.Hop();
break;
case B b:
b.Skip();
break;
case C _:
return new ArgumentException("Type C will be supported in the next version");
default:
return new ArgumentException("Unexpected type: " + o.GetType());
}
答案 11 :(得分:4)
C#8模式匹配的增强使其可以像这样进行。在某些情况下,它可以胜任工作,并且更加简洁。
public Animal Animal { get; set; }
...
var animalName = Animal switch
{
Cat cat => "Tom",
Mouse mouse => "Jerry",
_ => "unknown"
};
答案 12 :(得分:4)
使用C#7和模式匹配。
switch (foo.GetType())
{
case var type when type == typeof(Player):
break;
case var type when type == typeof(Address):
break;
case var type when type == typeof(Department):
break;
case var type when type == typeof(ContactType):
break;
default:
break;
}
答案 13 :(得分:4)
我要么
答案 14 :(得分:3)
答案 15 :(得分:3)
另一种方法是定义接口ITching,然后在两个类中实现它 这是狙击手:
public interface IThing
{
void Move();
}
public class ThingA : IThing
{
public void Move()
{
Hop();
}
public void Hop(){
//Implementation of Hop
}
}
public class ThingA : IThing
{
public void Move()
{
Skip();
}
public void Skip(){
//Implementation of Skip
}
}
public class Foo
{
static void Main(String[] args)
{
}
private void Foo(IThing a)
{
a.Move();
}
}
答案 16 :(得分:2)
从C#8开始,您可以使用新开关使其更加简洁。并使用丢弃选项_可以避免在不需要时创建不必要的变量,例如:
public abstract class BuildingBaseObject {
//Irrelevant Stuff
}
public class BuildingTile : BuildingBaseObject
{
public RuleTile ruleTile;
}
public class BuildingObject : BuildingBaseObject
{
public GameObject objectTile;
}
public class UIShipItem : MonoBehaviour {
Object buildingObject;
public object BuildingObject
{
get
{
if (buildingObject is BuildingObject) return buildingObject as BuildingObject;
else if (buildingObject is BuildingTile) return buildingObject as BuildingTile;
}
set
{
if(value is BuildingObject) buildingObject = value as BuildingObject;
if(value is BuildingTile) buildingObject = value as BuildingTile;
gameObject.GetComponent<Image>().sprite = (buildingObject as BuildingBaseObject).sprite;
}
}
}
Invoice和ShippingList是类,而document是可以是它们两者之一的对象。
答案 17 :(得分:2)
您可以创建重载方法:
void Foo(A a)
{
a.Hop();
}
void Foo(B b)
{
b.Skip();
}
void Foo(object o)
{
throw new ArgumentException("Unexpected type: " + o.GetType());
}
并使用dynamic参数类型来绕过静态类型检查:
Foo((dynamic)something);
答案 18 :(得分:2)
创建一个接口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());
}
}
}
请注意,最好使用“as”而不是首先使用“is”进行检查,然后进行投射,这样就可以进行2次投射(昂贵)。
答案 19 :(得分:2)
在这种情况下,我通常会得到一个谓词和操作列表。这些方面的东西:
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);
}
}
答案 20 :(得分:1)
您正在寻找Discriminated Unions
这是F#的语言功能,但您可以通过使用我制作的库来实现类似的效果,称为OneOf
https://github.com/mcintyre321/OneOf
switch
(以及if
和exceptions as control flow
)的主要优势在于它是编译时安全的 - 没有默认处理程序或通过
void Foo(OneOf<A, B> o)
{
o.Switch(
a => a.Hop(),
b => b.Skip()
);
}
如果向o添加第三项,则会出现编译错误,因为您必须在切换调用中添加处理程序Func。
您还可以执行返回值的.Match
,而不是执行语句:
double Area(OneOf<Square, Circle> o)
{
return o.Match(
square => square.Length * square.Length,
circle => Math.PI * circle.Radius * circle.Radius
);
}
答案 21 :(得分:1)
根据C#7.0规范,您可以声明范围为case
的{{1}}中的局部变量:
switch
您是否有机会问为什么将变量声明为object a = "Hello world";
switch (a)
{
case string _:
// The variable 'a' is a string!
break;
case int _:
// The variable 'a' is an int!
break;
case Foo _:
// The variable 'a' is of type Foo!
break;
}
?为什么要强调?
好吧,C#7.0引入的另一个功能是您可以将其命名为从未引用的变量。因此,您不能引用变量string _
。在OP要求的许多情况下,这是一件好事,因为他只想检查类型,而不要获取强制转换的引用。否则,您可以重命名该变量,并根据需要将其用作引用。
这是执行此操作的最佳方法,因为它仅涉及强制转换和堆栈上推操作,这是解释器仅在按位操作和_
条件之后才可以运行的最快操作。
将其与boolean
进行比较,这里的内存使用要少得多:持有字典需要在RAM中占用更多空间,而CPU需要更多的计算来创建两个数组(一个用于键,另一个用于值),并且收集键的哈希码,以将值放入各自的键中。
因此,据我所知,即使您不希望仅使用Dictionary<K, V>
-if
-then
,我也不认为会有更快的方法使用else
运算符进行阻止,如下所示:
is
答案 22 :(得分:1)
我将使用对您的开关有意义的任何名称和方法名称创建一个接口,让我们分别调用它们:IDoable
,告诉您实现void Do()
。
public interface IDoable
{
void Do();
}
public class A : IDoable
{
public void Hop()
{
// ...
}
public void Do()
{
Hop();
}
}
public class B : IDoable
{
public void Skip()
{
// ...
}
public void Do()
{
Skip();
}
}
并按如下所示更改方法:
void Foo<T>(T obj)
where T : IDoable
{
// ...
obj.Do();
// ...
}
至少在编译时您是安全的,我怀疑从性能角度来看,这比在运行时检查类型要好。
答案 23 :(得分:0)
这是一个替代答案,混合了JaredPar和VirtLink答案的贡献,具有以下约束:
用法:
var result =
TSwitch<string>
.On(val)
.Case((string x) => "is a string")
.Case((long x) => "is a long")
.Default(_ => "what is it?");
代码:
public class TSwitch<TResult>
{
class CaseInfo<T>
{
public Type Target { get; set; }
public Func<object, T> Func { get; set; }
}
private object _source;
private List<CaseInfo<TResult>> _cases;
public static TSwitch<TResult> On(object source)
{
return new TSwitch<TResult> {
_source = source,
_cases = new List<CaseInfo<TResult>>()
};
}
public TResult Default(Func<object, TResult> defaultFunc)
{
var srcType = _source.GetType();
foreach (var entry in _cases)
if (entry.Target.IsAssignableFrom(srcType))
return entry.Func(_source);
return defaultFunc(_source);
}
public TSwitch<TResult> Case<TSource>(Func<TSource, TResult> func)
{
_cases.Add(new CaseInfo<TResult>
{
Func = x => func((TSource)x),
Target = typeof(TSource)
});
return this;
}
}
答案 24 :(得分:0)
正如Pablo所说,界面方法几乎总是正确的做法来处理这个问题。要真正利用switch,另一种方法是在类中使用自定义枚举来表示您的类型。
enum ObjectType { A, B, Default }
interface IIdentifiable
{
ObjectType Type { get; };
}
class A : IIdentifiable
{
public ObjectType Type { get { return ObjectType.A; } }
}
class B : IIdentifiable
{
public ObjectType Type { get { return ObjectType.B; } }
}
void Foo(IIdentifiable o)
{
switch (o.Type)
{
case ObjectType.A:
case ObjectType.B:
//......
}
}
这也是在BCL中实现的。一个示例是MemberInfo.MemberTypes,另一个示例是基本类型GetTypeCode
,例如:
void Foo(object o)
{
switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode()
{
case TypeCode.Int16:
case TypeCode.Int32:
//etc ......
}
}
答案 25 :(得分:0)
是的-只需使用C#7向上的名称稍为奇怪的“模式匹配”即可在类或结构上进行匹配:
IObject concrete1 = new ObjectImplementation1();
IObject concrete2 = new ObjectImplementation2();
switch (concrete1)
{
case ObjectImplementation1 c1: return "type 1";
case ObjectImplementation2 c2: return "type 2";
}
答案 26 :(得分:0)
我用
public T Store<T>()
{
Type t = typeof(T);
if (t == typeof(CategoryDataStore))
return (T)DependencyService.Get<IDataStore<ItemCategory>>();
else
return default(T);
}
答案 27 :(得分:0)
应与
一起使用案例类型_:
喜欢:
int i = 1;
bool b = true;
double d = 1.1;
object o = i; // whatever you want
switch (o)
{
case int _:
Answer.Content = "You got the int";
break;
case double _:
Answer.Content = "You got the double";
break;
case bool _:
Answer.Content = "You got the bool";
break;
}
答案 28 :(得分:0)
如果您知道所期望的课程,但仍然没有对象,您甚至可以执行以下操作:
private string GetAcceptButtonText<T>() where T : BaseClass, new()
{
switch (new T())
{
case BaseClassReview _: return "Review";
case BaseClassValidate _: return "Validate";
case BaseClassAcknowledge _: return "Acknowledge";
default: return "Accept";
}
}
答案 29 :(得分:0)
我同意乔恩关于对课程名称进行操作的问题。如果保留模式,则可能需要考虑使用“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("...");
不同之处在于当你使用模式if(foo是Bar){((Bar)foo).Action();你正在进行两次类型转换。现在也许编译器会优化,只做一次工作 - 但我不会指望它。
答案 30 :(得分:0)
尝试这样:
public void Test(BaseType @base)
{
switch (@base)
{
case ConcreteType concrete:
DoSomething(concrete);
break;
case AnotherConcrete concrete:
DoSomething(concrete);
break;
}
}