Учитывая, что 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());
}
}
@jeyoung запечатанные классы, и это не стоит для специальных ситуаций
см. также stackoverflow.com/questions/156467/switch-pattern-matching-i деаstackoverflow.com/questions/7252186/switch-case-on-type-c-sh arpstackoverflow.com/questions/7542793/…stackoverflow.com/questions/4478464/c-sharp-switch-on-type
@jeyoung: Типичная ситуация, когда полиморфизм не может быть использован, - это когда переключаемые типы не должны знать код, содержащий оператор switch. Один пример: сборка А содержит набор объектов данных (который не будет изменяться, определенный в документе спецификации или подобном). Каждая сборка B, C и D ссылается на А и обеспечивает преобразование различных объектов данных из А (например, сериализацию / десериализацию в некоторый конкретный формат). Вам либо нужно отразить всю иерархию классов в B, C и D и использовать фабрики, либо у вас есть ...
... чтобы различать различные типы из А, чтобы надлежащим образом обрабатывать различные объекты в B, C и D.





Один из вариантов - иметь словарь от Type до Action (или другого делегата). Найдите действие по типу и затем выполните его. Я использовал это для заводов раньше.
Незначительное примечание: подходит для совпадений 1: 1, но может быть проблемой с наследованием и / или интерфейсами - тем более, что порядок не гарантированно сохраняется со словарем. Но все же я так делаю в нескольких местах ;-p Итак +1
@Marc: Как наследование или интерфейсы сломались бы в этой парадигме? Если предположить, что ключ - это тип, а действие - это метод, то, насколько я могу судить, наследование или интерфейсы фактически должны вызывать Right Thing (TM). Я, конечно, понимаю проблему с множественными действиями и отсутствием порядка.
Я много раз использовал эту технику в прошлом, обычно перед переходом на контейнер IoC.
Этот метод не подходит для наследования и интерфейсов, потому что вам нужно однозначное соответствие между проверяемым объектом и вызываемым делегатом. Какой из множества интерфейсов объекта вы должны попытаться найти в словаре?
Если вы создаете словарь специально для этой цели, вы можете перегрузить индексатор, чтобы вернуть значение типа ключа или, если он отсутствует, его суперкласс, если он отсутствует, то этот суперкласс и т.д., пока ничего не останется.
Итак, если у меня есть цепочка наследования, например: Object> Foo> Bar> Blee - и у меня есть действие, определенное для «Foo», то поиск по «Blee» или «Bar» найдет действие, назначенное для «Foo».
Создайте суперкласс (S) и сделайте его наследником A и B. Затем объявите абстрактный метод на S, который необходимо реализовать каждому подклассу.
При этом метод "foo" также может изменить свою подпись на Foo (S o), что сделает его безопасным по типу, и вам не нужно создавать это уродливое исключение.
Верно, bruno, но вопрос не предполагает этого. Вы можете включить это в свой ответ, хотя Пабло.
Из вопроса я думаю, что A и B достаточно общие, чтобы они могли быть A = String; B = List <int> например ...
С 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 вы можете использовать оператор switch с оператор nameof () (спасибо @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;
}
это работает с case typeof (string) .Name: ... или это должно быть с Valuetype?
Обфускация может сломать это
@TomerW Я считаю, что падеж должен быть постоянным.
Мне не нравится этот ответ, потому что nameof (NamespaceA.ClassC) == nameof (NamespaceB.ClassC) истинно.
(C# 7) вы также можете использовать подчеркивание, если вам не нужен доступ к объекту: case UnauthorizedException _:
Я знаю, что не должен комментировать бесполезно, но это отличная новая функция, которая очень и очень хорошо очищает код.
Я бы либо
В таких случаях я обычно получаю список предикатов и действий. Что-то в этом роде:
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);
}
}
Вам действительно следует перегрузить свой метод, а не пытаться устранить неоднозначность самостоятельно. Большинство ответов до сих пор не учитывают будущие подклассы, что в дальнейшем может привести к действительно ужасным проблемам с обслуживанием.
Разрешение перегрузки определяется статически, так что просто не сработает.
@Neutrino: в вопросе нет ничего, что указывало бы на то, что тип неизвестен во время компиляции. И если это так, перегрузка имеет больше смысла, чем любой другой вариант, учитывая исходный пример кода OP.
Я думаю, что тот факт, что он пытается использовать оператор if или switch для определения типа, является довольно четким признаком того, что тип неизвестен во время компиляции.
@Neutrino, я помню, как указал Сергей Березовский, в C# есть ключевое слово dynamic, которое представляет тип, который должен быть решен динамически (во время выполнения, а не во время компиляции).
Я согласен с Джоном по поводу наличия хеша действий для имени класса. Если вы сохраните свой шаблон, вы можете подумать об использовании вместо него конструкции «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 is Bar) {((Bar) foo) .Action (); } вы выполняете приведение типов дважды. Теперь, возможно, компилятор оптимизируется и выполнит эту работу только один раз, но я бы не стал на это рассчитывать.
Мне действительно не нравится несколько точек выхода (возвратов), но если вы хотите придерживаться этого, добавьте в начале «if (o == null) throw», так как позже вы не узнаете, неудачный ли приведение, или объект был нулевым.
Создайте интерфейс 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 кастинга, так что это дороже.
В C# (ОБНОВЛЕНИЕ: в C# 7 / VS 2017 поддерживается переключение типов - см. ответ Захари Йейтса ниже) однозначно отсутствует переключение типов. Чтобы сделать это без большого оператора if / else if / else, вам нужно будет работать с другой структурой. Некоторое время назад я написал сообщение в блоге, в котором подробно описывалось, как построить структуру TypeSwitch.
https://docs.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
};
}
}
"type == entry.Target" также можно изменить на "entry.Target.IsAssignableFrom (type)", чтобы учесть совместимые типы (например, подклассы).
Изменен код, чтобы использовать "entry.Target.IsAssignableFrom (type)", чтобы поддерживать подклассы.
Возможно, стоит отметить одну вещь: (насколько я понимаю) необходимо указывать действие «по умолчанию» последним, чтобы гарантировать, что все остальные случаи проверены. Я считаю, что это не является обязательным требованием для стандартного переключателя - не то чтобы я когда-либо видел, чтобы кто-то пытался установить «по умолчанию» где-нибудь, кроме дна. Пара отказоустойчивых вариантов для этого может заключаться в том, чтобы упорядочить массив, чтобы гарантировать, что значение по умолчанию является последним (немного расточительно), или вставьте значение по умолчанию в переменную, которая будет обрабатываться после foreach (что может произойти, только если совпадение не будет найдено)
В итоге я все время использовал такой подход, поэтому я заключил его в проект github.com/asgerhallas/ShinySwitch - и на nuget тоже: nuget.org/packages/ShinySwitch.
Другой способ - определить интерфейс 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();
}
}
Если бы вы использовали 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 его перегрузки разрешаются во время выполнения, а не во время компиляции. Я еще немного написал об этой идее совсем недавно. Опять же, я просто хотел бы повторить, что это, вероятно, работает хуже, чем все другие предложения, я предлагаю это просто из любопытства.
Сегодня у меня была такая же идея. Это примерно в 3 раза медленнее, чем включение имени типа. Конечно, медленнее - дело относительное (для 60 000 000 вызовов - всего 4 секунды), а код настолько удобочитаем, что это того стоит.
Учитывая, что наследование облегчает распознавание объекта как более одного типа, я думаю, что переключение может привести к плохой двусмысленности. Например:
Случай 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. Думаю, это было бы неправильно.
Вы можете подумать о проверке компилятором типов оператора "typewitch", проверяя, что перечисленные типы не наследуются друг от друга. Однако этого не существует.
foo is T - это не то же самое, что foo.GetType() == typeof(T) !!
С Ответ Джаредпара в затылке, я написал - вариант его класса 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);
}
}
}
Похоже на хорошее решение, и хотелось узнать, что еще вы сказали об этом, но блог мертв.
Блин, ты прав. У моего веб-хостинга проблемы уже через час. Они над этим работают. Сообщение в моем блоге по сути такое же, как и ответ здесь, но со ссылкой на полный исходный код.
Отлично, кажется, снова вернулся.
@Virtlink Спасибо за этот статический помощник, я сделал небольшую вилку вашей сути для совместимости с Silverlight (Windows Phone) и this.value только для чтения.
Мне нравится, как это сводит кучу скобок if до простого «функционального» переключателя. Хорошо сделано!
Вы также можете добавить метод расширения для начального случая: public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource. Это позволяет вам сказать value.Case((C x) ...
Кроме того, я думаю, вы можете просто сказать value is TTarget вместо всего этого IsAssignableFrom.
@JoeyAdams: Я включил ваше последнее предложение вместе с некоторыми небольшими улучшениями. Однако я сохраняю синтаксис прежним.
@Virtlink: Круто. Я согласен не использовать метод расширения Case, поэтому любой, кто сталкивается с кодом, использующим TypeSwitch, видит четкое указание на то, что используется служебный класс. Я полагаю, вы могли бы создать метод расширения TypeSwitch с той же сигнатурой, что и On (так что вы можете сказать value.TypeSwitch().Case((C x) ...), но это не имеет большого значения.
Как предполагает Пабло, интерфейсный подход почти всегда является правильным решением для решения этой проблемы. Чтобы действительно использовать переключатель, другой альтернативой является наличие настраиваемого перечисления, обозначающего ваш тип в ваших классах.
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 ......
}
}
Мне понравился Virtlink использование неявной типизации, который сделал коммутатор более читаемым, но мне не понравилось, что раннее отключение невозможно, и что мы занимаемся распределением. Давайте немного увеличим производительность.
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.
}
Ну, у меня пальцы болят. Сделаем это в Т4:
<#@ 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());
Читабельно и быстро. Теперь, когда все продолжают указывать в своих ответах и учитывая природу этого вопроса, порядок важен при сопоставлении типов. Следовательно:
Для встроенных типов можно использовать перечисление 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;
}
Спасибо за добавление варианта TypeCode () для примитивных типов, потому что даже вариант C# 7.0 с ними не работает (очевидно, и nameof ())
Это альтернативный ответ, который сочетает в себе ответы 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;
}
}
Да, благодаря C# 7 этого можно достичь. Вот как это делается (используя образец выражения):
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());
}
+1, см. Документация по функциям C# 7
Вы ищете 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 внутри вызова switch.
Вы также можете сделать .Match, который возвращает значение, а не выполняет инструкцию:
double Area(OneOf<Square, Circle> o)
{
return o.Match(
square => square.Length * square.Length,
circle => Math.PI * circle.Radius * circle.Radius
);
}
Согласно спецификации C# 7.0, вы можете объявить локальную переменную с областью действия в case из switch:
object a = "Hello world";
switch (a)
{
case string myString:
// The variable 'a' is a string!
break;
case int myInt:
// The variable 'a' is an int!
break;
case Foo myFoo:
// The variable 'a' is of type Foo!
break;
}
Это лучший способ сделать это, потому что он включает в себя только операции приведения и вставки в стек, которые являются самыми быстрыми операциями, которые интерпретатор может выполнять сразу после побитовых операций и условий boolean.
Сравнивая это с Dictionary<K, V>, здесь используется гораздо меньше памяти: для хранения словаря требуется больше места в ОЗУ и больше вычислений со стороны ЦП для создания двух массивов (один для ключей, а другой для значений) и сбора хэш-кодов для ключей к поместите значения в соответствующие ключи.
Итак, насколько мне известно, я не верю, что может существовать более быстрый способ, если вы не хотите использовать только блок if-then-else с оператором is следующим образом:
object a = "Hello world";
if (a is string)
{
// The variable 'a' is a string!
} else if (a is int)
{
// The variable 'a' is an int!
} // etc.
Да - просто используйте слегка странное название «сопоставление с образцом» из C# 7 и выше для сопоставления по классу или структуре:
IObject concrete1 = new ObjectImplementation1();
IObject concrete2 = new ObjectImplementation2();
switch (concrete1)
{
case ObjectImplementation1 c1: return "type 1";
case ObjectImplementation2 c2: return "type 2";
}
Я бы создал интерфейс с любым именем и именем метода, которые имели бы смысл для вашего коммутатора, давайте назовем их соответственно: 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();
// ...
}
По крайней мере, с этим вы в безопасности во время компиляции, и я подозреваю, что с точки зрения производительности это лучше, чем проверка типа во время выполнения.
я использую
public T Store<T>()
{
Type t = typeof(T);
if (t == typeof(CategoryDataStore))
return (T)DependencyService.Get<IDataStore<ItemCategory>>();
else
return default(T);
}
Вы можете использовать сопоставление с образцом в 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;
}
Спасибо за это! Может также использоваться для обнаружения подклассов: if (this.TemplatedParent.GetType (). IsSubclassOf (typeof (RadGridV iew))) можно изменить на: switch (this.TemplatedParent.GetType ()) case var subRadGridView when subRadGridView. IsSubclassOf (typeof (RadGridView)):
Ты делаешь это неправильно. См. ответ Serge Intern и прочтите о Принцип подстановки Лискова
Должен работать с
case type _:
нравиться:
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;
}
Если вы знаете ожидаемый класс, но у вас все еще нет объекта, вы можете сделать это даже так:
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";
}
}
Усовершенствования сопоставления с образцом в C# 8 сделали это возможным. В некоторых случаях он делает работу и более лаконичен.
public Animal Animal { get; set; }
...
var animalName = Animal switch
{
Cat cat => "Tom",
Mouse mouse => "Jerry",
_ => "unknown"
};
Начиная с C# 8, вы можете сделать его еще более кратким с помощью нового переключателя. А с помощью опции discard _ вы можете избежать создания ненужных переменных, когда они вам не нужны, например:
return document switch {
Invoice _ => "Is Invoice",
ShippingList _ => "Is Shipping List",
_ => "Unknown"
};
Invoice и ShippingList - это классы, а документ - это объект, который может быть любым из них.
Примечание: необходимо использовать _ (или имя переменной), иначе он не будет компилироваться. Легко пропустить.
Попробуйте пойти по этому пути:
public void Test(BaseType @base)
{
switch (@base)
{
case ConcreteType concrete:
DoSomething(concrete);
break;
case AnotherConcrete concrete:
DoSomething(concrete);
break;
}
}
Из любопытства, почему бы вам просто не использовать полиморфизм?