鉴于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());
}
}
c#中绝对没有类型切换(更新:在c# 7 / VS 2017中支持类型切换-参见Zachary Yates的回答)。为了不使用大量的if/else if/else语句,您需要使用不同的结构。前段时间我写了一篇博文,详细介绍了如何构建TypeSwitch结构。
https://learn.microsoft.com/archive/blogs/jaredpar/switching-on-types
短版本: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
{
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)不同!!
这是一个混合来自JaredPar和VirtLink答案的备选答案,具有以下约束:
开关结构表现为函数,并将函数作为参数接收到case。
确保它被正确地构建,并且始终存在一个默认函数。
它在第一次匹配后返回(对于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;
}
}
有了JaredPar的答案,我写了一个他的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()方法的顺序很重要。
获取我的TypeSwitch类的完整的注释代码。这是一个有效的缩写版本:
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);
}
}
}