С# - Назначение в выражении if
У меня есть класс Animal
и его подкласс Dog
.
Я часто нахожу себя в кодировке следующих строк:
if (animal is Dog)
{
Dog dog = animal as Dog;
dog.Name;
...
}
Для переменной Animal animal;
.
Есть ли какой-то синтаксис, который позволяет мне написать что-то вроде:
if (Dog dog = animal as Dog)
{
dog.Name;
...
}
Ответы
Ответ 1
Ответ ниже был написан много лет назад и обновлен с течением времени. Начиная с С# 7 вы можете использовать сопоставление с образцом:
if (animal is Dog dog)
{
// Use dog here
}
Обратите внимание, что dog
по-прежнему находится в области видимости после оператора if
, но определенно не назначен.
Нет, нет. Это более идиоматично, чтобы написать это, хотя:
Dog dog = animal as Dog;
if (dog != null)
{
// Use dog
}
Учитывая, что "как следует, если" почти всегда используется таким образом, может возникнуть больше смысла для того, чтобы существовал оператор, который выполняет обе части за один раз. В настоящее время это не С# 6, но может быть частью С# 7, если реализовано предложение соответствия шаблону.
Проблема заключается в том, что вы не можете объявить переменную в условной части инструкции if
1. Самый близкий подход, о котором я могу думать, заключается в следующем:
// EVIL EVIL EVIL. DO NOT USE.
for (Dog dog = animal as Dog; dog != null; dog = null)
{
...
}
Это просто противно... (Я только что попробовал, и это работает, но, пожалуйста, не делайте этого. О, и вы можете объявить dog
, используя var
, конечно.)
Конечно, вы можете написать метод расширения:
public static void AsIf<T>(this object value, Action<T> action) where T : class
{
T t = value as T;
if (t != null)
{
action(t);
}
}
Затем назовите его с помощью
animal.AsIf<Dog>(dog => {
// Use dog in here
});
В качестве альтернативы вы можете объединить два:
public static void AsIf<T>(this object value, Action<T> action) where T : class
{
// EVIL EVIL EVIL
for (var t = value as T; t != null; t = null)
{
action(t);
}
}
Вы также можете использовать метод расширения без выражения лямбда более чистым способом, чем цикл for:
public static IEnumerable<T> AsOrEmpty(this object value)
{
T t = value as T;
if (t != null)
{
yield return t;
}
}
Тогда:
foreach (Dog dog in animal.AsOrEmpty<Dog>())
{
// use dog
}
1 Вы можете назначать значения в операторах if
, хотя я редко это делаю. Это не то же самое, что объявление переменных. Для меня это не очень необычно сделать в while
, хотя при чтении потоков данных. Например:
string line;
while ((line = reader.ReadLine()) != null)
{
...
}
В эти дни я обычно предпочитаю использовать обертку, которая позволяет мне использовать foreach (string line in ...)
, но я рассматриваю это как довольно идиоматический шаблон. Обычно нехорошо иметь побочные эффекты в состоянии, но альтернативы обычно включают дублирование кода, и когда вы знаете этот шаблон, легко получить право.
Ответ 2
Если as
не работает, он возвращает null
.
Dog dog = animal as Dog;
if (dog != null)
{
// do stuff
}
Ответ 3
Вы можете присваивать значение переменной, если эта переменная уже существует. Вы также можете использовать переменную, чтобы позволить повторному использованию этого имени переменной позже в том же методе, если это проблема.
public void Test()
{
var animals = new Animal[] { new Dog(), new Duck() };
foreach (var animal in animals)
{
{ // <-- scopes the existence of critter to this block
Dog critter;
if (null != (critter = animal as Dog))
{
critter.Name = "Scopey";
// ...
}
}
{
Duck critter;
if (null != (critter = animal as Duck))
{
critter.Fly();
// ...
}
}
}
}
предполагая
public class Animal
{
}
public class Dog : Animal
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
Console.WriteLine("Name is now " + _name);
}
}
}
public class Duck : Animal
{
public void Fly()
{
Console.WriteLine("Flying");
}
}
получает вывод:
Name is now Scopey
Flying
Образец присвоения переменной в тесте также используется при чтении байтовых блоков из потоков, например:
int bytesRead = 0;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
// ...
}
Однако шаблон с расширенным диапазоном переменных, однако, не является особенно распространенным кодом кода, и если бы я видел, что он используется повсюду, я бы искал способ его реорганизации.
Ответ 4
Есть ли какой-то синтаксис, который позволяет мне написать что-то вроде:
if (Dog dog = animal as Dog) { ... dog ... }
?
Вероятно, в С# 6.0. Эта функция называется "выражения объявления". См.
https://roslyn.codeplex.com/discussions/565640
для деталей.
Предлагаемый синтаксис:
if ((var i = o as int?) != null) { … i … }
else if ((var s = o as string) != null) { … s … }
else if ...
В более общем плане, предлагаемая функция заключается в том, что в качестве выражения может использоваться объявление локальной переменной. Этот синтаксис if
является просто хорошим следствием более общей функции.
Ответ 5
Один из методов расширения, которые я нахожу, что я пишу и использую часто *, - это
public static TResult IfNotNull<T,TResult>(this T obj, Func<T,TResult> func)
{
if(obj != null)
{
return func(obj);
}
return default(TResult);
}
Что можно использовать в этой ситуации как
string name = (animal as Dog).IfNotNull(x => x.Name);
И тогда name
- это имя собаки (если это собака), в противном случае значение null.
* Я не знаю, является ли это исполнителем. Он никогда не возникал как узкое место в профилировании.
Ответ 6
Против зерна здесь, но, возможно, вы делаете это неправильно, в первую очередь. Проверка типа объекта почти всегда является запахом кода. Не все ли животные, в вашем примере, имеют имя? Затем просто вызовите Animal.name, не проверяя, является ли это собакой или нет.
Альтернативно, инвертируйте метод так, чтобы вы вызывали метод на Animal, который делает что-то по-другому в зависимости от конкретного типа Animal. См. Также: Полиморфизм.
Ответ 7
Более короткое выражение
var dog = animal as Dog
if(dog != null) dog.Name ...;
Ответ 8
Вот еще один грязный код (не такой грязный, как Jon's, хотя:-)) зависит от модификации базового класса. Я думаю, что он фиксирует намерение, хотя, возможно, не хватает смысла:
class Animal
{
public Animal() { Name = "animal"; }
public List<Animal> IfIs<T>()
{
if(this is T)
return new List<Animal>{this};
else
return new List<Animal>();
}
public string Name;
}
class Dog : Animal
{
public Dog() { Name = "dog"; }
public string Bark { get { return "ruff"; } }
}
class Program
{
static void Main(string[] args)
{
var animal = new Animal();
foreach(Dog dog in animal.IfIs<Dog>())
{
Console.WriteLine(dog.Name);
Console.WriteLine(dog.Bark);
}
Console.ReadLine();
}
}
Ответ 9
Если вам нужно сделать несколько таких, как-ifs один за другим (и использование полиморфизма не является вариантом), рассмотрите конструкцию SwitchOnType.
Ответ 10
Проблема (с синтаксисом) не связана с назначением, так как оператор присваивания в С# является допустимым выражением. Скорее, это с желаемым объявлением, так как объявления являются инструкциями.
Если я должен написать такой код, я иногда (в зависимости от более широкого контекста) напишу код следующим образом:
Dog dog;
if ((dog = animal as Dog) != null) {
// use dog
}
Есть достоинства с указанным выше синтаксисом (который близок к запрошенному синтаксису), потому что:
- Использование
dog
вне if
приведет к ошибке компиляции, поскольку ей не присвоено значение в другом месте. (То есть не назначайте dog
в другом месте.)
- Этот подход также может быть хорошо добавлен к
if/else if/...
(для выбора подходящей ветки требуется только столько as
, что большой случай, когда я пишу его в этой форме, когда я должен.)
- Предотвращает дублирование
is/as
. (Но также сделано с формой Dog dog = ...
.)
- Не отличается от "идиоматического". (Просто не увлекайтесь: сохраняйте условное в согласованной форме и просто.)
Чтобы действительно изолировать dog
от остального мира, можно использовать новый блок:
{
Dog dog = ...; // or assign in `if` as per above
}
Bite(dog); // oops! can't access dog from above
Счастливое кодирование.
Ответ 11
Другое решение EVIL с методами расширения:)
public class Tester
{
public static void Test()
{
Animal a = new Animal();
//nothing is printed
foreach (Dog d in a.Each<Dog>())
{
Console.WriteLine(d.Name);
}
Dog dd = new Dog();
//dog ID is printed
foreach (Dog dog in dd.Each<Dog>())
{
Console.WriteLine(dog.ID);
}
}
}
public class Animal
{
public Animal()
{
Console.WriteLine("Animal constructued:" + this.ID);
}
private string _id { get; set; }
public string ID { get { return _id ?? (_id = Guid.NewGuid().ToString());} }
public bool IsAlive { get; set; }
}
public class Dog : Animal
{
public Dog() : base() { }
public string Name { get; set; }
}
public static class ObjectExtensions
{
public static IEnumerable<T> Each<T>(this object Source)
where T : class
{
T t = Source as T;
if (t == null)
yield break;
yield return t;
}
}
Я лично предпочитаю чистый путь:
Dog dog = animal as Dog;
if (dog != null)
{
// do stuff
}
Ответ 12
Оператор if не позволяет этого, но цикл for будет.
например.
for (Dog dog = animal as Dog; dog != null; dog = null)
{
dog.Name;
...
}
В случае, если он работает, это не сразу очевидно, а вот пошаговое объяснение процесса:
- Переменная собака создается как собака типа и назначается переменное животное
который передается Собаку.
- Если присваивание не выполнено, собака имеет значение null, что предотвращает содержимое
цикла for из цикла, потому что он сразу же разбивается
из.
- Если присваивание завершается успешно, цикл for пробегает
итерации.
- В конце итерации переменной собаки присваивается значение
null, который вырывается из цикла for.
Ответ 13
using(Dog dog = animal as Dog)
{
if(dog != null)
{
dog.Name;
...
}
}
Ответ 14
IDK, если это помогает кому угодно, но вы всегда можете попытаться использовать TryParse для назначения вашей переменной. Вот пример:
if (int.TryParse(Add(Value1, Value2).ToString(), out total))
{
Console.WriteLine("I was able to parse your value to: " + total);
} else
{
Console.WriteLine("Couldn't Parse Value");
}
Console.ReadLine();
}
static int Add(int value1, int value2)
{
return value1 + value2;
}
Общая переменная будет объявлена перед вашим оператором if.