# 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
```