Включению типов определенно недостает C# ( ОБНОВЛЕНИЕ: в C#7 / VS 2017, включая типы поддерживается - , см. ответ Zachary Yates ниже ). Чтобы сделать это без большого если/еще, если/еще оператор, необходимо будет работать с различной структурой. Я записал сообщению в блоге некоторое время назад детализацию, как создать структуру TypeSwitch.
http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx
Короткая версия: TypeSwitch разработан, чтобы предотвратить избыточный кастинг и дать синтаксис, который подобен нормальному переключателю/оператору выбора. Например, вот TypeSwitch в действии со стандартным событием
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"));
формы Windows, код для 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
};
}
}
Я соглашаюсь с Jon о наличии хеша действий к имени класса. Если Вы сохраняете свой шаблон, Вы могли бы хотеть рассмотреть использование "как" конструкция вместо этого:
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("...");
различие то, что при использовании скороговорки если (нечто является Панель) {((Панель) нечто).Action ();} Вы делаете преобразование типа дважды. Теперь, возможно, компилятор оптимизирует и только сделает ту работу однажды - но я не рассчитывал бы на него.
Создайте интерфейс 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());
}
}
}
Примечание, которое лучше использовать "как" вместо этого сначала сверение "," и затем кастинг как тот способ, которым Вы делаете 2 броска (дорогими).
Иначе должен был бы определить интерфейс IThing и затем реализовать, он в обоих классах вот отрывок:
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();
}
}
Я такие случаи я обычно заканчиваю со списком предикатов и действий. Что-то вдоль этих строк:
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);
}
}
Я был бы любой
Необходимо действительно перегружать метод, не пытаясь сделать разрешение неоднозначности сами. Большинство ответов до сих пор не принимает во внимание будущие подклассы, которые могут привести к действительно ужасным проблемам обслуживания позже.
При использовании 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 динамический вызов метода разрешили его перегрузки в а не время компиляции во время выполнения. Я записал немного больше об этой идее совсем недавно . Снова, я был бы, точно так же, как, чтобы повторить, что это, вероятно, работает хуже, чем все другие предложения, я предлагаю его просто как любопытство.
Создайте суперкласс (S) и сделайте A, и B наследовались ему. Тогда объявите абстрактный метод для S, который должен реализовать каждый подкласс.
Выполнение этого метод "нечто" может также изменить свою подпись на Foo (S o), делая его безопасным с точки зрения типов, и Вы не должны выдавать то ужасное исключение.
Одна опция состоит в том, чтобы иметь словарь от Type
до Action
(или некоторый другой делегат). Ищите действие на основе типа, и затем выполните его. Я использовал это для фабрик до настоящего времени.
С C# 7, который поставил с Visual Studio 2017 (Выпуск 15.*), Вы в состоянии использовать Типы в 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 (), оператор (благодарит @Joey Adams):
switch(o.GetType().Name) {
case nameof(AType):
break;
case nameof(BType):
break;
}
С C# 5 и ранее, Вы могли использовать оператор переключения, но необходимо будет использовать волшебную строку, содержащую имя типа..., которое не является, особенно осуществляют рефакторинг дружественный (благодарит @nukefusion)
switch(o.GetType().Name) {
case "AType":
break;
}
Я посмотрел на несколько опций здесь, зеркально отразив то, что может сделать F#. F# имеет намного лучшую поддержку основанного на типе переключения (хотя я все еще придерживаюсь C#;-p). Вы могли бы хотеть видеть здесь и здесь .