# Operadores ### switch Na versão 7.0 do C# foi introduzido o conceito de `Pattern Matching`, que tem o intuito de evitar a necessidade da implementação de `typecast`. Já na versão 8.0, este conceito foi aplicado, sendo aplicado ao condicional `switch`, com a introdução das `switch expressions`. No `switch` clássico, utilizamos cases para comparar uma variável/objeto em relação a uma série de valores: ```csharp= public Formato SelecionarFormato(object item) { var formato = item as FormatoPlanta?; if (formato == null) return null; Formato formatoSelecionado = null; switch (formato) { case FormatoPlanta.Quadrado: formatoSelecionado = new Quadrado(Largura, Altura); break; case FormatoPlanta.Retangulo: formatoSelecionado = new Retangulo(Largura, Altura); break; case FormatoPlanta.Triangulo: formatoSelecionado = new Triangulo(Largura, Altura, 2); break; } return formatoSelecionado; } ``` Com o pattern matching a conversão realizada no início do método acima pode ser substituída por um if: ```csharp= public Formato SelecionarFormato(object item) { if(item is FormatoPlanta formato) { Formato formatoSelecionado = null; switch (formato) { case FormatoPlanta.Quadrado: formatoSelecionado = new Quadrado(Largura, Altura); break; case FormatoPlanta.Retangulo: formatoSelecionado = new Retangulo(Largura, Altura); break; case FormatoPlanta.Triangulo: formatoSelecionado = new Triangulo(Largura, Altura, 2); break; } return formatoSelecionado; } else return null; } ``` Ela também pode ser aplicada diretamente no switch: ```csharp= public Formato SelecionarFormato(object item) { switch (item) { case FormatoPlanta formato when formato is FormatoPlanta.Quadrado: return new Quadrado(Largura, Altura); case FormatoPlanta formato when formato is FormatoPlanta.Retangulo: return new Retangulo(Largura, Altura); case FormatoPlanta formato when formato is FormatoPlanta.Triangulo: return new Triangulo(Largura, Altura, 2); default: return null; } } ``` Agora que conhecemos as opções disponíveis atualmente, vamos conhecer a switch expression. > Para fazer uso das switch expressions, a aplicação precisa ser configurada para o C# 8.0 O código pode ser refatorado novamente para a switch expression: ```csharp= public Formato SelecionarFormato(object item) { return item switch { FormatoPlanta.Quadrado => new Quadrado(Largura, Altura), FormatoPlanta.Retangulo => new Retangulo(Largura, Altura), FormatoPlanta.Triangulo => new Triangulo(Largura, Altura, 2), _ => null }; } ``` Note que a variável/objeto a ser analisado vem antes da cláusula switch. Foi dispensado o uso do case, é informado apenas os valores a serem testados. No lugar de dois pontos, utiliza-se o =>. Para o valor padrão (default), é utilizado o underline. Assim como no switch padrão, as opções são analisadas na ordem que forem informadas, então a opção padrão sempre deve ser a última da expressão. Da mesma forma que o operador ternário, o resultado da switch expression precisa ser atribuída a uma variável (ou ser retornada como no exemplo acima). Por fim, no exemplo apresentado aqui não ocorre as situações, mas caso a cláusula switch esteja comparando uma propriedade do objeto: ```csharp= string Display(object o) { switch (o) { case Ponto p when p.X == 0 && p.Y == 0: return "origem"; //... } } ``` Pode ser aplicado o property pattern: ```csharp= string Display(object o) { return o switch { Ponto { X: 0, Y: 0 } => "origem", Ponto { X: var x, Y: var y } => $"({x}, {y})", {} => o.ToString(), // `o` não é nulo, mas não é do tipo Ponto null => "Nulo" }; } ``` Ou o desconstrutor: ```csharp= string Display(int x, int y) => (x, y) switch { (0, 0) => "origem", //... }; ``` > [Para fazer uso das switch expressions, a aplicação precisa ser configurada para o C# 8.0](https://www.treinaweb.com.br/blog/switch-expressions-no-c-8-0) ### with Disponível em C# 9.0 e posterior, uma with produz uma cópia de uma instância de registro existente, com propriedades e campos especificados modificados. ```csharp= using System; public class WithExpressionBasicExample { public record NamedPoint(string Name, int X, int Y); public static void Main() { var p1 = new NamedPoint("A", 0, 0); Console.WriteLine($"{nameof(p1)}: {p1}"); // output: p1: NamedPoint { Name = A, X = 0, Y = 0 } var p2 = p1 with { Name = "B", X = 5 }; Console.WriteLine($"{nameof(p2)}: {p2}"); // output: p2: NamedPoint { Name = B, X = 5, Y = 0 } var p3 = p1 with { Name = "C", Y = 4 }; Console.WriteLine($"{nameof(p3)}: {p3}"); // output: p3: NamedPoint { Name = C, X = 0, Y = 4 } Console.WriteLine($"{nameof(p1)}: {p1}"); // output: p1: NamedPoint { Name = A, X = 0, Y = 0 } } } ``` A with pode definir propriedades posicionais ou propriedades criadas usando a sintaxe de propriedade padrão. As propriedades `Non-positional` devem ter `init` um `set` para ser alterado em uma with. Para implementar esse recurso, o compilador sintetiza um método de clone e um construtor de cópia. O método de clone virtual retorna um novo registro inicializado pelo construtor de cópia. Quando você usa uma expressão, o compilador cria um código que chama o método clone e define as propriedades with especificadas. ### x ?? y O operador de coalescência nula `??` retornará o valor do operando esquerdo se não for null. Caso contrário, ele avaliará o operando direito e retornará seu resultado. O operador `??` não avaliará o operando do lado direito se o operando esquerdo for avaliado como não nulo. Disponível em C# 8.0 e posterior, o operador de atribuição de União nula `??=` atribui o valor de seu operando à direita para seu operando à esquerda somente se o operando esquerdo for avaliado como null. O operador `??=` não avaliará o operando do lado direito se o operando esquerdo for avaliado como não nulo. ```csharp= List<int> numbers = null; int? a = null; (numbers ??= new List<int>()).Add(5); Console.WriteLine(string.Join(" ", numbers)); // output: 5 numbers.Add(a ??= 0); Console.WriteLine(string.Join(" ", numbers)); // output: 5 0 Console.WriteLine(a); // output: 0 ``` ### Is O operador "is" é usado para verificar se o tipo de tempo de execução de um objeto é compatível com um determinado tipo ou não. Então basicamente usamos o operador "is" para verificar se o tipo de objeto é o que esperamos que seja. ```csharp= using System; class Class1 { ... } class Class2 { ... } public class IsTest { public static void Test(object o) { if (o is Class1 a) { Console.WriteLine("o é Class1"); a.Execute(); } else if (o is Class2 b) { Console.WriteLine("o é Class1"); b.Execute(); } else { Console.WriteLine(";-;"); } } ``` ### As O operador "as" converte explicitamente o resultado de uma expressão para uma determinada referência ou tipo de valor anulável. Se a conversão não for possível, o operador as retorna null. ```csharp= IEnumerable<int> numbers = new[] { 10, 20, 30 }; IList<int> indexable = numbers as IList<int>; if (indexable != null) { Console.WriteLine(indexable[0] + indexable[indexable.Count - 1]); // output: 40 } ``` ## Shift - Operadores definidos apenas para os tipos int, uint, long e ulong, por isso o resultado sempre contem pelo menos 32bits. - Quando ambos operandos forem de outros tipos integrais (sbyte, byte, short, ushort ou char), converte pra int. - Quando são de tipos integrais diferentes, são convertidos para o valor mais próximo que o contém. - Console.WriteLine($"{Convert.ToString(var, toBase: 2)}"); ### Deslocamento à esquerda << Alterna o operando esquerdo para a esquerda pelo número de bits definidos pelo seu operando a direita. Ele descarta os bits que estão fora do intervalo do tipo de resultado, e os bits inferiores ficam como 0. * Exemplo do funcionamento ```csharp= uint x = 0b_1100_1001_0000_0000_0000_0000_0001_0001; uint y = x << 4; // antes: 11001001000000000000000000010001 // depois: 10010000000000000000000100010000 // 1100{1001000000000000000000010001}0000 ``` * Exemplo de conversão ```csharp= char x = 'a'; var y = x << 8; y.GetType(); // System.Int32 // antes: 01100001 // depois: 110000100000000 ``` ### Deslocamento à direita >> O >> operador alterna seu operando à esquerda à direita pelo número de bits definidos pelo seu operando à direita. Descarta os bits de ordem inferior. - Com os tipos uint ou ulong o operador right-shift executará um deslocamento lógico, onde os bits de ordem superior ficarão como 0. ```csharp= uint x = 0b_1001101; uint y = x >> 3; // Antes: 1001101 // Depois: 1001 ``` - Com os tipos int ou long , o operador right-shift executará um deslocamento aritmético, ou seja, as posições vazias de bit de ordem superior são definidas como zero se o operando à esquerda for positivo e definidas como um se ele for negativo. ```csharp= int a = -2147483648; int b = a >> 3; // Antes: 10000000000000000000000000000000 // Depois: 11110000000000000000000000000000 // Com o número 83648, seria: // Antes: 10100011011000000 // Depois: 101000110110000 ``` ## Operadores Lógicos ### Operador AND lógico& - O operador & computa o AND lógico de seus operandos. O resultado de x & y será true se ambos x e y forem avaliados como true. Caso contrário, o resultado será false. ```csharp= bool SecondOperand() { Console.WriteLine("Second operand is evaluated."); return true; } bool a = false & SecondOperand(); Console.WriteLine(a); // Output: // Second operand is evaluated. // False bool b = true & SecondOperand(); Console.WriteLine(b); // Output: // Second operand is evaluated. // True ``` - O & operador computa os operandos lógicos e de seus integrantes de bit que se encontram. ```csharp= uint a = 0b_1111_1000; uint b = 0b_1001_1101; uint c = a & b; Console.WriteLine(Convert.ToString(c, toBase: 2)); // Output: // 10011000 ``` - O operador unário & retorna o endereço de seu operando. ```csharp= unsafe { int number = 27; int* pointerToNumber = &number; Console.WriteLine($"Value of the variable: {number}"); Console.WriteLine($"Address of the variable: {(long)pointerToNumber:X}"); } // Output is similar to: // Value of the variable: 27 // Address of the variable: 6C1457DBD4 ``` ### Operador OR exclusivo lógico ^ - O operador ^ computa o OR exclusivo lógico, também conhecido como o **XOR lógico**, de seus operandos. O resultado de x ^ y é true se x é avaliado como true e y avaliado como false, ou x avaliado como false e y avaliado como true. Caso contrário, o resultado será false. Ou seja, para os operandos bool, o operador ^ **computa o mesmo resultado que o operador de desigualdade** !=. ```csharp= Console.WriteLine(true ^ true); // output: False Console.WriteLine(true ^ false); // output: True Console.WriteLine(false ^ true); // output: True Console.WriteLine(false ^ false); // output: False ``` - O ^ operador computa a lógica or exclusiva de bits de bit, também conhecida como o XOR lógico de bit-a, de seus operandos integrantes. ```csharp= uint a = 0b_1111_1000; uint b = 0b_0001_1100; uint c = a ^ b; Console.WriteLine(Convert.ToString(c, toBase: 2)); // Output: // 11100100 ``` ### Operador OR lógico | - O operador | computa o OR lógico de seus operandos. O resultado de x | y será true se x ou y for avaliado como true. Caso contrário, o resultado será false. ```csharp= bool SecondOperand() { Console.WriteLine("Second operand is evaluated."); return true; } bool a = true | SecondOperand(); Console.WriteLine(a); // Output: // Second operand is evaluated. // True bool b = false | SecondOperand(); Console.WriteLine(b); // Output: // Second operand is evaluated. // True ``` - O | operador computa o OR lógico de bits de bit ou de seus operandos inteiros: ```csharp= uint a = 0b_1010_0000; uint b = 0b_1001_0001; uint c = a | b; Console.WriteLine(Convert.ToString(c, toBase: 2)); // Output: // 10110001 ```