Можно ли получить доступ к различным полям, таким как возраст собаки или окрас кошки, каждого класса непосредственно из этого списка?
Да, но только если вы знать типы во время компиляции.
C# (пока) не поддерживает правильную алгебру c -типирование или дискриминируемые объединения, поэтому для исчерпывающей проверки вам необходимо использовать is
оператор (в идеале с пакетом анализа кода, например ExhaustiveMatching ) или определите свой собственный Match
метод.
Примерно так:
Подход 1: Использование оператора is
:
Такое использование оператора is
иногда называют «сопоставлением с образцом», но я не согласен с этим термином, потому что на самом деле это просто эргономичное c улучшение синтаксиса для проверки типов во время выполнения, а не реальный (в смысле Haskell) сопоставление с образцом по данным.
List<Animal> animals = ...
foreach( Animal a in animals )
{
if( a is Cat cat )
{
Console.WriteLine( "Cat breed: {0}.", cat.Breed );
}
else if( a is Dog dog )
{
Console.WriteLine( "Dog breed: {0}.", dog.Breed );
}
else if( a is Bird bird )
{
Console.WriteLine( "Bird name: {0}.", bird.Name );
}
else
{
throw new InvalidOperationException( "Unknown animal subclass." );
}
}
Пакеты анализа кода, такие как ExhaustiveMatching , выдадут вам предупреждения или ошибки во время компиляции если у вас закрыто ed определена иерархия типов (например, классы перечисления в стиле Java), которые вы переключаете (с помощью оператора C# 7.x switch
с case TypeName name:
-синтаксисом), но с отсутствующими подклассами.
Подход 2: Использование пользовательского метода Match / Switch:
Определение пользовательского метода Match
или Switch
позволяет вам потребовать исчерпывающего использования сайтов вызовов, но это зависит от вашего Match
/ Switch
метод является исчерпывающим, но поскольку он перекладывает бремя ответственности на вас, а не на потребителя, этот подход имеет преимущества.
Вот так:
abstract class Animal
{
public TResult Match<TResult>(
Func<Cat ,TResult> isCat,
Func<Dog ,TResult> isDog,
Func<Bird,TResult> isBird
)
{
if ( this is Cat c ) return isCat( c );
else if( this is Dog d ) return isDog( d );
else if( this is Bird b ) return isBird( b );
else throw new InvalidOperationException( "Unknown animal subclass." );
}
}
Используется так:
foreach( Animal a in animals )
{
String summary = a.Match(
isCat : c => "Cat breed: " + c.Breed,
isDog : d => "Dog breed: " + d.Breed,
isBird: b => "Bird name: " + b.Name,
);
Console.WriteLine( summary );
}