Estruturas exóticas na programação c#¶
A plataforma .net é fundamentalmente arquitetada para um modelo de programação orientada a objetos. Aspectos como herança, polimorfismo, interfaces, generics estão bem documentados e são bem desenvolvidos nesta arquitetura. Existe, entretanto, diversas estruturas de programação que não são do domínio da Orientação Objeto. Os casos descritos abaixo explicam alguns destas estruturas:
tipos primitivos¶
Tipos primitivos como inteiros, lógicos, caractere, data, hora dentre outros são alias de suas respectivas implementações formais como classes.
Dito de outra forma:
public sealed class Teste01{
public static void Main(string[] args){
int valor = 1;
System.Int32 valor1 = 0;
bool testarIgualdade = valor1==valor;
// Irá exibir -> true
}
}
A utilização de primitivos e das suas contrapartes orientadas a objeto é intercambiável e não é necessário operações de conversão ou cast.
interfaces¶
Interfaces em .net são uma palavra reservada na linguagem e um contrato que suporta a definição de métodos, eventos e properties. Mas não suporta fields.
Interfaces suportam herança e generics, mas não polimorfismo. Também não é possível criar instância anônima ou explícita a partir de um tipo interface.
Interfaces são estruturas para definição e posterior implementação de contratos de funcionamento e a sua implementação pode ser realizada tanto em structs quanto em classes.
structs ⧉¶
É uma estrutura da linguagem c# que não suporta vários dos paradigmas da orientação objeto. Formalmente, um struct é um tipo de dado definido por valor, não por referência!
Algumas características peculiares do struct:
- Não podem ser herdados.
- Pode implementar uma ou mais interfaces, normalmente.
- Não é removido pelo Garbage Collector.
- Pode ser declarado como imutável, através do modificador readonly.
- Caso a igualdade e o hashcode não sejam redeclarados, estas operações são calculadas usando os atributos que forem definidos pelo struct.
redefinição de operadores¶
Formalmente, é denominado de override de operadores ⧉
É um suporte da linguagem que permite que operações unárias, comparação e igualdade sejam redeclaradas!
- A redeclaração de operadores se aplica tanto aos tipo
class
,struct
erecord
. - São sempre estáticos
- Operadores de comparação e de igualdade devem ser implementados aos pares, isto é, a implementação de == requer a implementação de !=. A implementação de >= requer a implementação de <=.
- Operadores aritméticos também podem ser redeclarados.
- cast implícito e explícito também podem ser controlados com este tipo de operador.
Cuidado com redeclaração de operadores de cast!
A implementação de cast explícito e implícitos pode deixar seu código ilegível ou difícil de depurar!
Alguns exemplos para estas operações.
/// <summary>
/// Representa uma questão num questionário sub-indice
/// </summary>
[DataContract]
public partial class Questao : IEquatable<Questao>
{
public string Id { get; set; }
public int IdQuestionario { get; set; }
public bool Equals(Questao? other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return
string.Equals(Id, other.Id, StringComparison.OrdinalIgnoreCase)
&& string.Equals(IdQuestionario, other.IdQuestionario, StringComparison.OrdinalIgnoreCase);
}
public static bool operator ==(Questao? left, Questao? right)
{
return Equals(left, right);
}
public static bool operator !=(Questao? left, Questao? right)
{
return !Equals(left, right);
}
}
E um exemplo mais complexo, utilizando soma, subtração, comparação.
public readonly struct BRL:
IEquatable<BRL>,
IComparable<BRL>,
IComparable
{
private readonly decimal _inner;
private BRL(decimal valor)
{
_inner = valor;
}
public static readonly BRL Zero = new BRL(0);
public static readonly BRL Um = new BRL(1);
public static BRL Create(decimal valor)
{
return new BRL(Money.Create(valor, Currency.BRL));
}
#region Equality members
public bool Equals(BRL other)
{
return _inner.Equals(other._inner);
}
public override bool Equals(object obj)
{
return obj is BRL other && Equals(other);
}
public override int GetHashCode()
{
return _inner.GetHashCode();
}
public int CompareTo(object obj)
{
if (obj is null) { return 1; }
if (obj is BRL other)
return CompareTo(other);
throw new ArgumentException($"Argument must be {GetType().Name}.", nameof(obj));
}
public static bool operator ==(BRL left, BRL right)
{
return left.Equals(right);
}
public static bool operator !=(BRL left, BRL right)
{
return !left.Equals(right);
}
public static bool operator >(BRL left, BRL right)
{
return left._inner > right._inner;
}
public static bool operator <(BRL left, BRL right)
{
return left._inner < right._inner;
}
public static bool operator >=(BRL left, BRL right)
{
return left._inner > right._inner;
}
public static bool operator <=(BRL left, BRL right)
{
return left._inner < right._inner;
}
public int CompareTo(BRL other)
{
return this._inner.CompareTo(other._inner);
}
#endregion
[Pure]
public static BRL operator +(BRL left, BRL right)
{
return new BRL(left._inner+right._inner);
}
[Pure]
public static BRL operator *(BRL left, BRL right)
{
return new BRL(left._inner + right._inner);
}
[Pure]
public static BRL operator *(BRL left, Quantidade right)
{
return new BRL(left._inner * (decimal)right);
}
[Pure]
public static BRL operator +(BRL left, Porcentagem right)
{
return new BRL(
left._inner + (left._inner*Math.Abs((decimal)right))/100)
);
}
[Pure]
public BRL Descontar(Porcentagem percentual)
{
return new BRL(
this._inner - (left._inner*Math.Abs((decimal)right))/100)
);
}
[Pure]
public BRL Adicionar(Porcentagem percentual)
{
return new BRL(
this._inner + (left._inner*Math.Abs((decimal)right))/100)
);
}
[Pure]
public static BRL operator -(BRL left, BRL right)
{
return new BRL(left._inner - right._inner);
}
}
Extension methods¶
Suporte de mixin ⧉ nativo do compilador. Largamente disseminado na bibliotecas nativas e em pacotes de terceiros, é um recurso que permite um desenho mais compacto de classes e operações e deixa em aberto a criação de combinações para casos específicos através de extension methods. Mais informações aqui ⧉.
namespace StringUtils
{
public static class StringExtensions
{
public static int WordCount(this string str)
{
return str.Split(new char[] { ' ', '.', '?' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
public static bool OrdinalEquals(this string str, string other)
{
return string.Equals(str, other, StringComparison.OrdinalEquals);
}
}
public class TestExtension{
public void Test1(){
var umaPalavra = "outra palavra longa";
var outraPalavra = "outra palavra longa";
var count = "uma palavra longa".WordCount();
// esperado: 3
var iguais = umaPalavra.OrdinalEquals(outraPalavra);
// esperado: false
}
}
}
Inicialização de objetos¶
Inicializadores de objetos e coleções ⧉ são um recurso de linguagem que permite uma notação mais compacta para inicialização de objetos e coleções.
Instruções como:
private AvaliacaoEfetiva GerarAvaliacao(DefModeloAvaliacao defAvaliacao)
{
var result = new AvaliacaoEfetiva();
result.CodigoIndice = defAvaliacao.CodigoIndice;
result.IndiceCalculado = 0M;
result.NomeExibicao = defAvaliacao.NomeExibicao;
result.Situacao = SituacaoAvaliacao.PreenchimentoDisponivel;
result.QtdItensConsiderados = 0;
result.Versao = 0;
result.Items = defAvaliacao.Questoes.Select(
defItem =>
{
var avaliacao = new ItemAvaliacao();
avaliacao.Versao = 0;
avaliacao.CodigoAvaliacao = null;
avaliacao.CodigoItem = defItem.CodigoItem;
avaliacao.Situacao = SituacaoAvaliacaoItem.PreenchimentoDisponivel;
avaliacao.JustificativaAtribuida = string.Empty;
avaliacao.Ordinal = defItem.Ordinal;
avaliacao.PesoAtribuido = null;
avaliacao.PesoConsiderado = null;
avaliacao.TextoApresentacao = defItem.TextoApresentado;
return avaliacao;
}).ToList();
return result;
}
Podem ser simplificadas para :
private AvaliacaoEfetiva GerarAvaliacao(DefModeloAvaliacao defAvaliacao)
{
var result = new AvaliacaoEfetiva()
{
CodigoIndice = defAvaliacao.CodigoIndice,
IndiceCalculado = 0M,
NomeExibicao = defAvaliacao.NomeExibicao,
Situacao = SituacaoAvaliacao.PreenchimentoDisponivel,
QtdItensConsiderados = 0,
Versao = 0,
Items = defAvaliacao.Questoes.Select(
defItem => new ItemAvaliacao()
{
Versao = 0,
CodigoAvaliacao = null,
CodigoItem = defItem.CodigoItem,
Situacao = SituacaoAvaliacaoItem.PreenchimentoDisponivel,
JustificativaAtribuida = string.Empty,
Ordinal = defItem.Ordinal,
PesoAtribuido = null,
PesoConsiderado = null,
TextoApresentacao = defItem.TextoApresentado
}).ToList()
};
return result;
}
O mesmo vale para inicialização de coleções.
var identity = new Matrix
{
[0, 0] = 1.0,
[0, 1] = 0.0,
[0, 2] = 0.0,
[1, 0] = 0.0,
[1, 1] = 1.0,
[1, 2] = 0.0,
[2, 0] = 0.0,
[2, 1] = 0.0,
[2, 2] = 1.0,
};
e dicionários:
var pares = new Dictionary<int, string>
{
{19, "dezenove" },
{23, "vinte e três" },
{42, "quarenta e dois" }
};
Classes parciais¶
Classes e métodos parciais ⧉ são um recurso de organização de código fonte da linguagem. Permite que um ou mais arquivos de código estejam ligados ao mesmo class
, struct
, interface
ou record
. Métodos parciais também são um recurso de organização: Pode-se fazer a declaração de um método na parte em que sua declaração seja mais esclarecedora, porém sua implementação pode estar localizada em outra parte do class
, struct
, interface
ou record
.
Classes e métodos parciais são um recurso de codificação. Uma vez compilados, os objetos e métodos são "ajuntados" num tipo apenas.
Exemplo:
Uma vez compilado, estas instruções são convertidas assim:
Cuidado com este recurso!
Mesmo que sejam um recurso organizacional importante, classes e métodos parciais não substituem boas práticas de codificação como SOLID, DRY e ACID!
Linq e live collections¶
- É uma extensão funcional inspirada por especificações como OQL.
- Uma visão geral sobre o Linq pode ser conferida na documentação da Microsoft ⧉.
- É um poderoso mecanismo de consultas a objetos. Pode ser utilizado sobre collections, NoSQL records, SGBD records, File System Resources, Xml Entities, Byte Streams
- Não está diretamente relacionado ao acesso de dados em fontes externas (out-of-process). É fundamentalmente construído para operações sobre objetos carregados na memória (objetos In-Process. Não confundir com In-Memory Database).
- IQueryable é a principal interface habilitadora de utilização de recursos
- Pode ser utilizado através de conjuntos de API específicas estruturadas na forma de Fluent Programming
- Pode ser utilizado através de uma sintaxe não imperativa suportadas pelos compiladores para .net (C#, VB.net, F#) e se assemelha à sintaxe da linguagem SQL.
- É arquiteturalmente construída para permitir execução tardia
- É . Esse é um aspecto que deve ser de permanente atenção, uma vez que:
- Tem impacto direto sobre o montante de memória utilizado pela aplicação.
- A estrutura de execução tardia, associado a rotinas de aquisição de registros externos (cascade loading, entity graph hidratation, lazy loading) pode gerar processamento de expressivo volume de dados sem que o programador se aperceba do que está acontecendo. (exemplos)
using, lock, unsafe, yield¶
São palavras reservadas da linguagem para criar blocos "seguros" de execução de uma ou mais instruções.
using¶
O using
é utilizado como um bloco de operações seguras com objetos que fazem alocação de recursos externos do tipo IDisposable
. Exemplo:
string filePath = "example.txt";
string textToWrite = "Hello, this is a test message!";
// Use the using statement to ensure the StreamWriter is properly disposed of
using (StreamWriter writer = new StreamWriter(filePath))
{
writer.WriteLine(textToWrite);
}
A implementação acima é um açúcar sintático para esta operação.
string filePath = "example.txt";
string textToWrite = "Hello, this is a test message!";
// Use the using statement to ensure the StreamWriter is properly disposed of
StreamWriter writer = null;
try{
writer = new StreamWriter(filePath);
writer.WriteLine(textToWrite);
}finally{
if (writer!=null)
writer.Dispose();
}
Nota-se portanto, que a condensação das instruções na palavra reservada using
previne o "esquecimento" da finalização determinística do objeto writer. Esta finalização seria realizada de qualquer modo, mas apenas quando o Garbage Collector recolhesse a variável writer. Até lá, uma conexão com um dispositivo remoto permaneceria aberta.
lock¶
Da mesma maneira que using
, a instrução lock
⧉ estabelece uma região de código que será executada se, e apenas, uma primitiva de sincronização entre threads puder ser obtida. Ao final das instruções, de maneira equivalente ao using
, o lock liberará a primitiva de sincronização.
Exemplo com lock
:
O mesmo código, sem a instrução lock
:
object __lockObj = x;
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
// Your code...
}
finally
{
if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
}
unsafe¶
Embora ponteiros não sejam um elemento da linguagem c# (nem do .net como um todo), c# permite a execução de instruções com ponteiros em regiões unsafe
⧉. Isso é extremamente útil para interface com aplicações e bibliotecas escritos em plataformas nativas, como c/c++.
yield¶
A palavra reservada yield ⧉ habilita um recurso de programação conhecido como "live collection". Live collections são coleções que serão efetivamente avaliadas apenas quando os seus iteradores forem percorridos.
Assim, a instrução:
using System.Linq;
var todos = new int[]{1,2,3,4,5,6,7,8,9};
var itens = todos.Avaliar(item=> item %2 ==0 );
Só trará os elementos [2, 4, 6, 8]
se mais alguma ação de "consumo" (count, any, foreach, while) for realizada sobre a variável itens
. De outra forma, a coleção todos
jamais será percorrida.
Por detrás do extension method Avaliar, há uma avaliação "preguiçosa" da coleção todos
, que é mais ou menos escrita assim:
public static IEnumerable<int> Avaliar(this IEnumerable<int> colecao, Func<int, bool> condicao)
{
foreach(var item in colecao)
{
if (condicao(item))
yield return item;
else
yield break;
}
}
Diferente das instruções using
e lock
, a instrução yield
cria uma máquina de estados durante a compilação. Esse processo é transparente e os detalhes fogem ao escopo dessa explicação.
https://www.csharptutorial.net/csharp-linq/linq-intersect/#:~:text=The%20Intersect()%20method%20uses,if%20they%20are%20primitive%20types ⧉. https://www.learnentityframeworkcore.com/configuration/fluent-api/ignore-method#:~:text=EF%20Core%20Ignore&text=One%20belongs%20to%20the%20ModelBuilder,exclude%20individual%20properties%20from%20mapping ⧉.
https://www.learnrazorpages.com/ ⧉