С# - Назначение в выражении 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.