Pular para conteúdo

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 e record.
  • 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:

[SerializableAttribute]
partial class Moon { }

[ObsoleteAttribute]
partial class Moon { }

Uma vez compilado, estas instruções são convertidas assim:

[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }

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:

using (x.EnterScope())
{
    // Your code...
}

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/ ⧉

https://www.learnblazor.com/ ⧉

https://shipilev.net/blog/2014/exceptional-performance/ ⧉