Atribuição em uma instrução if


142

Eu tenho uma classe Animale sua subclasse Dog. Costumo encontrar-me codificando as seguintes linhas:

if (animal is Dog)
{
    Dog dog = animal as Dog;    
    dog.Name;    
    ... 
}

Para a variável Animal animal;.

Existe alguma sintaxe que me permite escrever algo como:

if (Dog dog = animal as Dog)
{    
    dog.Name;    
    ... 
}

1
O que isso significaria? Qual seria a boolcondição?
21411 Kirk Woll

Nada que eu saiba. Algum motivo para não mudar o Nome para Animal?
AlG

22
Apenas uma observação, código como geralmente pode ser o resultado da quebra de um dos princípios do SOLID . O princípio da substituição L - Liskov . Não dizendo que é errado fazer o que você está fazendo o tempo todo, mas pode valer a pena pensar nisso.
Ckittel

por favor, tome nota do que @ckittel está fazendo, você provavelmente não quer fazer isso
khebbie

1
@ Sol não null,! = falseEm C #; O C # permite apenas bools reais ou coisas implicitamente conversíveis em bools em ifcondições. Nulos nem nenhum dos tipos inteiros são implicitamente convertíveis em bools.
Roman Starkov

Respostas:


323

A resposta abaixo foi escrita anos atrás e atualizada ao longo do tempo. No C # 7, você pode usar a correspondência de padrões:

if (animal is Dog dog)
{
    // Use dog here
}

Observe que dogainda está no escopo após a ifinstrução, mas não está definitivamente atribuído.


Não, não existe. É mais idiomático escrever isso:

Dog dog = animal as Dog;
if (dog != null)
{
    // Use dog
}

Dado que "como seguido de se" é quase sempre usado dessa maneira, pode fazer mais sentido haver um operador que execute as duas partes de uma só vez. No momento, isso não está no C # 6, mas pode fazer parte do C # 7, se a proposta de correspondência de padrões for implementada.

O problema é que você não pode declarar uma variável na parte da condição de uma ifinstrução 1 . A abordagem mais próxima que consigo pensar é a seguinte:

// EVIL EVIL EVIL. DO NOT USE.
for (Dog dog = animal as Dog; dog != null; dog = null)
{
    ...
}

Isso é desagradável ... (Eu apenas tentei, e funciona. Mas, por favor, não faça isso. Ah, e você pode declarar dogusando, é varclaro).

Claro que você pode escrever um método de extensão:

public static void AsIf<T>(this object value, Action<T> action) where T : class
{
    T t = value as T;
    if (t != null)
    {
        action(t);
    }
}

Em seguida, chame-o com:

animal.AsIf<Dog>(dog => {
    // Use dog in here
});

Como alternativa, você pode combinar os dois:

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);
    }
}

Você também pode usar um método de extensão sem uma expressão lambda de maneira mais limpa que o loop for:

public static IEnumerable<T> AsOrEmpty(this object value)
{
    T t = value as T;
    if (t != null)
    {
        yield return t;
    }
}

Então:

foreach (Dog dog in animal.AsOrEmpty<Dog>())
{
    // use dog
}

1 Você pode atribuir valores em ifdeclarações, embora raramente o faça. Isso não é o mesmo que declarar variáveis. Não é muito incomum para mim fazê-lo de uma vez whileao ler fluxos de dados. Por exemplo:

string line;
while ((line = reader.ReadLine()) != null)
{
    ...
}

Hoje em dia, normalmente prefiro usar um invólucro que me permita usar, foreach (string line in ...)mas vejo o acima como um padrão bastante idiomático. É geralmente não bom ter efeitos colaterais dentro de uma condição, mas as alternativas geralmente envolvem a duplicação de código, e quando você sabe que este padrão é fácil de acertar.


76
+1 por dar uma resposta e também implorar que o OP não a use. Clássico instantâneo.
Ckittel

8
@ Paul: Se eu estivesse tentando vendê- lo para alguém, eu não recomendaria fortemente que não o usassem. Estou apenas mostrando o que é possível .
Jon Skeet

12
@Paul: Eu acho que essa pode ter sido a motivação por trás EVIL EVIL EVIL, mas não sou positivo.
Adam Robinson

18
Eu criei um método de extensão semelhante (com várias sobrecargas) há um tempo atrás e os chamei AsEither(...), acho que é um pouco mais claro do que AsIf(...), para que eu possa escrever myAnimal.AsEither(dog => dog.Woof(), cat => cat.Meeow(), unicorn => unicorn.ShitRainbows()).
herzmeister

97
Esse é o melhor abuso de c # que já vi há algum tempo. Claramente você é um gênio do mal.
precisa

48

Se asfalhar, ele retornará null.

Dog dog = animal as Dog;

if (dog != null)
{
    // do stuff
}

Primeiro obrigado. Segundo, quero criar a variável dog no escopo da ifdeclaração e não no escopo externo.
22611 Michael

@ Michael, você não pode fazer isso em uma declaração if. O if deve ter um resultado booliano e não uma atribuição. Jon Skeet fornece algumas boas combinações genéricas e lambda que você pode considerar também.
Rodney S. Foley

ifpode ter um resultado booleano e uma atribuição. Dog dog; if ((dog = animal as Dog) != null) { // Use Dog }mas isso ainda introduz a variável no escopo externo.
Tom Mayfield

12

Você pode atribuir o valor à variável, desde que a variável já exista. Você também pode definir o escopo da variável para permitir que o nome da variável seja usado novamente mais tarde no mesmo método, se houver algum problema.

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();
                // ...
            }
        }
    }
}

assumindo

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");
    }
}

obtém saída:

Name is now Scopey
Flying

O padrão de atribuição de variável no teste também é usado ao ler blocos de bytes de fluxos, por exemplo:

int bytesRead = 0;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0) 
{
    // ...
}

O padrão de escopo da variável usado acima, no entanto, não é um padrão de código particularmente comum e, se eu o visse sendo usado em todo o lugar, estaria procurando uma maneira de refatorá-lo.


11

Existe alguma sintaxe que me permite escrever algo como:

if (Dog dog = animal as Dog) { ... dog ... }

?

Provavelmente haverá no C # 6.0. Esse recurso é chamado de "expressões de declaração". Vejo

https://roslyn.codeplex.com/discussions/565640

para detalhes.

A sintaxe proposta é:

if ((var i = o as int?) != null) {  i  }
else if ((var s = o as string) != null) {  s  }
else if ...

De maneira mais geral, o recurso proposto é que uma declaração de variável local possa ser usada como expressão . Essa ifsintaxe é apenas uma boa consequência do recurso mais geral.


1
À primeira vista, isso parece menos legível do que apenas declarar a variável como você faria hoje. Você saberia por que esse recurso em particular conseguiu passar da barra de -100 pontos?
asawyer

3
@asawyer: Primeiro, esse é um recurso solicitado com muita frequência. Segundo, outros idiomas têm essa extensão para "se"; O gcc, por exemplo, permite o equivalente em C ++. Terceiro, o recurso é mais geral do que apenas "se", como observei. Quarto, há uma tendência no C # desde o C # 3.0 de fazer cada vez mais coisas que exigiam um contexto de instrução, ao invés de um contexto de expressão; isso ajuda na programação de estilo funcional. Veja as notas de design do idioma para mais detalhes.
Eric Lippert

2
@asawyer: De nada! Sinta-se livre para participar da discussão no Roslyn.codeplex.com, se você tiver mais comentários. Além disso, acrescentaria: Quinto, a nova infraestrutura de Roslyn reduz os custos marginais para a equipe de implementação executar esses tipos de pequenos recursos experimentais, o que significa que a magnitude dos "menos 100" pontos é diminuída. A equipe está aproveitando a oportunidade para explorar pequenos recursos perfeitamente decentes que foram solicitados há muito tempo, mas nunca chegaram acima da barreira de -100 pontos anteriormente.
Eric Lippert

1
Os leitores desses comentários, que estão confusos sobre os "pontos" sobre os quais estamos falando, devem ler a postagem do blog do ex-designer de C # Eric Gunnerson sobre este tópico: blogs.msdn.com/b/ericgu/archive/2004/01/12/57985. aspx . Isso é uma analogia; não há "pontos" reais sendo contados.
Eric Lippert

@ asawyer: Eu acho que esse recurso realmente brilha nas chamadas para Try*(por exemplo, TryParse). Esse recurso não apenas faz essas chamadas em uma única expressão (como deveriam ser, IMO), mas também permite um escopo mais limpo dessas variáveis. Estou entusiasmado por ter o outparâmetro de um Trymétodo com escopo condicional; isso dificulta a introdução de certos tipos de bugs.
Brian

9

Um dos métodos de extensão que me vejo escrevendo e usando frequentemente * é

public static TResult IfNotNull<T,TResult>(this T obj, Func<T,TResult> func)
{
    if(obj != null)
    {
        return func(obj);
    }
    return default(TResult);
}

O que poderia ser usado nessa situação como

string name = (animal as Dog).IfNotNull(x => x.Name);

E então nameé o nome do cachorro (se for um cachorro), caso contrário nulo.

* Eu não tenho idéia se isso é de alto desempenho. Nunca surgiu como um gargalo na criação de perfil.


2
+1 para a nota. Se nunca surgir como um gargalo na criação de perfil, é um bom sinal de que é suficientemente eficiente .
Cody Gray

Por que você usaria o defaultValue como argumento e deixaria o chamador decidir o que eu z em vez de voltar ao padrão (....)?
Trident D'Gao

5

Indo contra a corrente aqui, mas talvez você esteja fazendo errado em primeiro lugar. Verificar o tipo de objeto é quase sempre um cheiro de código. Todos os animais, no seu exemplo, não têm um nome? Em seguida, basta chamar Animal.name, sem verificar se é um cachorro ou não.

Como alternativa, inverta o método para chamar um método em Animal que faça algo diferente, dependendo do tipo concreto do animal. Veja também: Polimorfismo.


4

Declaração mais curta

var dog = animal as Dog
if(dog != null) dog.Name ...;

3

Aqui estão alguns códigos sujos adicionais (embora não tão sujos quanto os de Jon :-)) dependentes da modificação da classe base. Eu acho que captura a intenção, embora talvez não entenda:

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();
    }
}

3

Se você precisar fazer vários como-se um após um (e usar polimorfismo não é uma opção), considere usar uma construção SwitchOnType .


3

O problema (com a sintaxe) não está na atribuição, pois o operador de atribuição em C # é uma expressão válida. Pelo contrário, é com a declaração desejada, pois as declarações são declarações.

Se eu devo escrever um código assim, às vezes (dependendo do contexto maior), escrevo o código assim:

Dog dog;
if ((dog = animal as Dog) != null) {
    // use dog
}

Há méritos com a sintaxe acima (que é próxima da sintaxe solicitada) porque:

  1. Usar dog fora do ifresultará em um erro de compilação, pois ele não recebe um valor em outro lugar. (Ou seja, não designe para dogoutro lugar.)
  2. Essa abordagem também pode ser bem expandida para if/else if/...(Existem apenas as asnecessárias para selecionar uma ramificação apropriada; esse é o grande caso em que eu a escrevo neste formulário quando necessário).
  3. Evita duplicação de is/as. (Mas também feito com Dog dog = ...forma.)
  4. Não é diferente de "tempo idiomático". (Apenas não se empolgue: mantenha a condicional de forma consistente e simples.)

Para realmente isolar dogdo resto do mundo, um novo bloco pode ser usado:

{
  Dog dog = ...; // or assign in `if` as per above
}
Bite(dog); // oops! can't access dog from above

Feliz codificação.


O ponto 1 que você oferece é a primeira coisa que me veio à mente. Declare a variável, mas atribua apenas no if. A variável então não pode ser referenciada de fora do if sem um erro do compilador - perfeito!
Ian Yates

1

você pode usar algo assim

// Declarar variável bool temp = false;

 if (previousRows.Count > 0 || (temp= GetAnyThing()))
                                    {
                                    }

0

Outra solução MAU com métodos de extensão :)

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;
    }
}

Pessoalmente, prefiro o jeito limpo:

Dog dog = animal as Dog;

if (dog != null)
{
    // do stuff
}

0

Uma instrução if não permitirá isso, mas um loop for permitirá.

por exemplo

for (Dog dog = animal as Dog; dog != null; dog = null)
{
    dog.Name;    
    ... 
}

Caso a maneira como funcione não seja imediatamente óbvia, aqui está uma explicação passo a passo do processo:

  • Cão variável é criado como tipo cão e atribuído à variável animal que é lançada em Cão.
  • Se a atribuição falhar, o cão é nulo, o que impede a execução do conteúdo do loop for, porque é imediatamente interrompido.
  • Se a atribuição for bem-sucedida, o loop for será executado na
    iteração.
  • No final da iteração, é atribuído à variável dog um valor nulo, que sai do loop for.

0
using(Dog dog = animal as Dog)
{
    if(dog != null)
    {
        dog.Name;    
        ... 

    }

}

0

IDK, se isso ajudar alguém, mas você sempre pode tentar usar um TryParse para atribuir sua variável. Aqui está um exemplo:

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;
    }

A variável total seria declarada antes da sua instrução if.


0

Acabei de descrever a instrução if para criar uma linha de código que se parece com o que você está interessado. Isso apenas ajuda a comprimir o código e achei mais legível, especialmente ao aninhar atribuições:

var dog = animal as Dog; if (dog != null)
{
    Console.WriteLine("Parent Dog Name = " + dog.name);

    var purebred = dog.Puppy as Purebred; if (purebred != null)
    {
         Console.WriteLine("Purebred Puppy Name = " + purebred.Name);
    }

    var mutt = dog.Puppy as Mongrel; if (mutt != null)
    {
         Console.WriteLine("Mongrel Puppy Name = " + mutt.Name);
    }
 }

0

Eu sei que estou super atrasada para a festa, mas achei que iria postar minha própria solução alternativa para esse dilema, já que ainda não a vi aqui (ou em qualquer outro lugar).

/// <summary>
/// IAble exists solely to give ALL other Interfaces that inherit IAble the TryAs() extension method
/// </summary>
public interface IAble { }

public static class IAbleExtension
{
    /// <summary>
    /// Attempt to cast as T returning true and out-ing the cast if successful, otherwise returning false and out-ing null
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="able"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public static bool TryAs<T>(this IAble able, out T result) where T : class
    {
        if (able is T)
        {
            result = able as T;
            return true;
        }
        else
        {
            result = null;
            return false;
        }
    }

    /// <summary>
    /// Attempt to cast as T returning true and out-ing the cast if successful, otherwise returning false and out-ing null
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="obj"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public static bool TryAs<T>(this UnityEngine.Object obj, out T result) where T : class
    {
        if (obj is T)
        {
            result = obj as T;
            return true;
        }
        else
        {
            result = null;
            return false;
        }
    }
}

Com isso, você pode fazer coisas como:

if (animal.TryAs(out Dog dog))
{
    //Do Dog stuff here because animal is a Dog
}
else
{
    //Cast failed! animal is not a dog
}

NOTA IMPORTANTE: Se você deseja usar TryAs () usando uma interface, DEVE ter essa interface herdando o IAble.

Aproveitar! 🙂

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.