<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vitor Luiz Rubio</title>
    <description>The latest articles on DEV Community by Vitor Luiz Rubio (@vitorrubio).</description>
    <link>https://dev.to/vitorrubio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F318817%2F3ce8ab9b-2c9c-4905-a8c2-3917be3375d0.JPG</url>
      <title>DEV Community: Vitor Luiz Rubio</title>
      <link>https://dev.to/vitorrubio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vitorrubio"/>
    <language>en</language>
    <item>
      <title>Struct para gerenciar Tags no C# - Parte 3</title>
      <dc:creator>Vitor Luiz Rubio</dc:creator>
      <pubDate>Sat, 11 Feb 2023 02:03:06 +0000</pubDate>
      <link>https://dev.to/vitorrubio/struct-para-gerenciar-tags-no-c-53c1</link>
      <guid>https://dev.to/vitorrubio/struct-para-gerenciar-tags-no-c-53c1</guid>
      <description>&lt;p&gt;Continuando com as alterações na struct tags, temos mais duas iterações. Depois é apenas documentar, melhorar os testes e arrumar o layout do código. Dê uma olhada depois no projeto no &lt;a href="https://github.com/vitorrubio/TagStructure"&gt;github&lt;/a&gt; para ver como ficou. &lt;/p&gt;




&lt;h3&gt;
  
  
  Iteração 5
&lt;/h3&gt;

&lt;p&gt;Adicionei overlod para os operadores de conversão implícita string =&amp;gt; tags e tags =&amp;gt; string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        public static implicit operator string(Tags t) =&amp;gt; t.ToString();

        public static implicit operator Tags(string s) =&amp;gt; new Tags(s);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E Deu pau boniiiiito.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Muito cuidado ao brincar com overload de operadores.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Primeiro de tudo, o que são conversões implícitas? São conversões sem perda de dados, de tipos compatíveis, que podem ser feitas diretamente sem colocar o tipo na frente. &lt;br&gt;
Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;byte a = 5;
int b = a;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa é uma conversão implícita. Você pode jogar um byte em um int, porque ele cabe, e ele também é um "tipo do mesmo tipo". Ele é um número inteiro. Só que menor. Ele pode ser convertido direto, é o mesmo tipo de dado. &lt;br&gt;
Tá, mas e uma conversão explícita? É o tipo de conversão que você tem que forçar com o nome do outro tipo na frente, porque os tipos são quase incompatíveis, ou compatíveis até certo ponto, e você vai perder dados. Por exemplo na conversão abaixo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;double a = 5.5;
int b = (int)a;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nessa conversão eu tenho que forçar o tipo double a "caber" no int e pra isso eu perco informação, eu perco o .5.&lt;/p&gt;

&lt;p&gt;No nosso caso, eu quero que Tags seja conversível para string. Assim de uma forma que se eu jogar uma string em uma tag ele crie a tag automaticamente sem dar new, e se eu jogar uma tag em uma string ele converta automaticamente sem eu ter que chamar o .ToString().&lt;/p&gt;

&lt;p&gt;Muito ousado? &lt;/p&gt;

&lt;p&gt;O que eu quero é fazer isso ser legal e compilável:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tags a = "vitor,teste";
string b = a;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E porque está dando vários erros do tipo &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Error CS0121  The call is ambiguous between the following methods or properties: 'Tags.RemoveTags(Tags?)' and 'Tags.RemoveTags(params string[]?)' TagStructureTest    C:\Users\vitor\OneDrive\Desktop\Labs\TagStructureTest\TagStructureTest\TagsTest.cs  32  N/A&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Onde está a ambiguidade? Em todos os métodos que eu aceito Tags como parâmetro de entrada mas que também tem um overload que aceite string está dando ambiguidade. Por quê? Porque o compilador não sabe se &lt;code&gt;RemoveTags(new Tags("tag5"));&lt;/code&gt;é pra chamar o RemoveTags de string ou o RemoveTags de Tags convertento pra string .... já que agora Tags é legalmente conversível pra string ... &lt;/p&gt;

&lt;p&gt;Complicado? Muito. &lt;/p&gt;

&lt;p&gt;Vamos retirar todos os métodos que aceitam Tags como argumento, e seus testes. Vamos também renomear AddTags para Add e RemoveTags para Remove.&lt;/p&gt;

&lt;p&gt;Magicamente removendo esses métodos ele compila, e agora temos menos métodos para dar manutenção, testar e documentar.&lt;/p&gt;

&lt;p&gt;Rodamos os testes e todos os testes rodaram exceto o &lt;code&gt;InequalityOperatorSameVarTest&lt;/code&gt;, que já esperávamos, pois Tags ainda não é imutável, e o teste&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        [TestMethod]
        public void TagsShouldBeEqualsToString()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Assert.IsTrue(tags1.Equals("tag1,tag2,tag3,tag4,tag5"));
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags1);
            //Assert.IsTrue(tags1 == "tag1,tag2,tag3,tag4,tag5"); //não compila
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Falha na linha &lt;code&gt;Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags1);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Estrano isso, mas vamos descomentar  linha Assert.IsTrue(tags1 == "tag1,tag2,tag3,tag4,tag5") e ver se ela passa, vamos também olhar a documentação do Assert.AreEqual e debugar. Vamos também dividir isso aí em 3 testes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        [TestMethod]
        public void TagsShouldBeEqualsToString()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Assert.IsTrue(tags1.Equals("tag1,tag2,tag3,tag4,tag5"));
        }

        [TestMethod]
        public void TagsShouldBeEqualsToStringUsingEqualityOperators()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Assert.IsTrue(tags1 == "tag1,tag2,tag3,tag4,tag5"); 
        }

        [TestMethod]
        public void ATagsVarShouldBeEqualsToASameContentString()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags1);
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compila e &lt;code&gt;Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags1);&lt;/code&gt; continua falhando. &lt;/p&gt;

&lt;p&gt;Inspecionando o método Assert.AreEqual encontramos a resposta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        //
        // Summary:
        //     Tests whether the specified objects are equal and throws an exception if the
        //     two objects are not equal. *Different numeric types are treated as unequal even
        //     if the logical values are equal. 42L is not equal to 42.*
        //
        // Parameters:
        //   expected:
        //     The first object to compare. This is the object the tests expects.
        //
        //   actual:
        //     The second object to compare. This is the object produced by the code under test.
        //
        // Exceptions:
        //   T:Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException:
        //     Thrown if expected is not equal to actual.
        public static void AreEqual(object expected, object actual)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja, Assert.AreEqual considera o mesmo tipo e mesmo valor. Ele falha pra um int e um byte por exemplo. Isso está na documentação.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.testtools.unittesting.assert.areequal?redirectedfrom=MSDN&amp;amp;view=visualstudiosdk-2022#overloads"&gt;Método Assert.AreEqual&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Então vamos trocar essa linha para  &lt;code&gt;Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags1.ToString());&lt;/code&gt; porque nós queremos ver se o valor das duas strings geradas é o mesmo. &lt;/p&gt;

&lt;p&gt;Funcionou. Agora vamos para a iteração 6 onde deixamos isso tudo verdadeiramente imutável. &lt;/p&gt;

&lt;h3&gt;
  
  
  Iteração 6
&lt;/h3&gt;

&lt;p&gt;Lembrando que cada interação minha é um ajuste ou refactoring para fazer funcionar um dos testes (e as vezes testes adicionais) e que eu estou colocando cada uma em uma branch. &lt;br&gt;
Eu não colocaria cada um em uma branch num projeto da vida real mas com certeza teria um commit bem explicado para cada um. &lt;br&gt;
Vamos começar fazendo o seguinte: Mudar o HashSet pra um ImmutableHashSet. Isso faz com que não possamos mais usar os métodos UnionWith e ExceptWith. Então teriamos que criar novos em vez de mudar seu conteúdo. Mas lemre-se que não devemos criar novos reference types dentro de um field de um calue type. &lt;br&gt;
Então teremos que fazer com que os métodos Add e Remove retornem um novo Tags com seu conteúdo já preparado (o resultado da fusão dos hashsets). E teremos que mudar a classe Produto também. &lt;br&gt;
Muita coisa terá que mudar. &lt;br&gt;
O parameterless constructor (construtor padrão) tem que criar um ImmutableHashSet vazio. O construtor que aceita params string[] deve criar um ImmutableHashSet com essas strings ou vazio. &lt;br&gt;
Os métodos add e remove devem aproveitar o método new pra criar um novo. O jeito de criar um ImmutableHashSet é meio diferente, você verá. &lt;/p&gt;

&lt;p&gt;Todos os tags.Add e tags.Remove tiveram que mudar para tags = tags.Add e tags = tags.Remove.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using System.Collections.Immutable;

namespace SharpTags
{
    public struct Tags
    {
        private readonly ImmutableHashSet&amp;lt;string&amp;gt; _taglist;

        public Tags() : this(null)
        {

        }

        public Tags(IEnumerable&amp;lt;string&amp;gt;? t) : this(t?.ToArray())
        {

        }

        public Tags(params string[]? t)
        {
            if (t != null &amp;amp;&amp;amp; t.Count() &amp;gt; 0)
            {
                var tagsToAdd = t.Where(x =&amp;gt; !string.IsNullOrWhiteSpace(x)).SelectMany(x =&amp;gt; x!.Split(",")).Select(x =&amp;gt; x.Trim().ToLower()).ToArray();
                _taglist = ImmutableHashSet.Create&amp;lt;string&amp;gt;(tagsToAdd);
            }
            else
            {
                _taglist = ImmutableHashSet.Create&amp;lt;string&amp;gt;();
            }
        }

        public override string ToString()
        {
            return string.Join(",", _taglist.Select(x =&amp;gt; x.Trim().ToLower()).OrderBy(x =&amp;gt; x).Distinct());
        }


        public Tags Add(IEnumerable&amp;lt;string&amp;gt;? t)
        {
            return this.Add(t?.ToArray());
        }

        public Tags Add(params string[]? t)
        {
            if (t != null &amp;amp;&amp;amp; t.Length &amp;gt; 0)
            {
                var tagsToAdd = t.Where(x =&amp;gt; !string.IsNullOrWhiteSpace(x)).SelectMany(x =&amp;gt; x!.Split(",")).Select(x =&amp;gt; x.Trim().ToLower());
                return new Tags(_taglist.Union(tagsToAdd));
            }

            return new Tags(_taglist);
        }


        public Tags Remove(IEnumerable&amp;lt;string&amp;gt;? t)
        {
            return this.Remove(t?.ToArray());
        }






        public Tags Remove(params string[]? t)
        {
            if (t != null &amp;amp;&amp;amp; t.Length &amp;gt; 0)
            {
                var tagsToRemove = t.Where(x =&amp;gt; !string.IsNullOrWhiteSpace(x)).SelectMany(x =&amp;gt; x!.Split(",")).Select(x =&amp;gt; x.Trim().ToLower());
                return new Tags(_taglist.Except(tagsToRemove));
            }
            return new Tags(_taglist);
        }


        public string[] GetTags()
        {
            return this._taglist.Select(x =&amp;gt; x.Trim().ToLower()).OrderBy(x =&amp;gt; x).Distinct().ToArray();
        }


        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        public override bool Equals(object? obj)
        {

            if (obj == null)
            {
                return false;
            }

            if ((!(obj is Tags)) &amp;amp;&amp;amp; (!(obj is string)))
            {
                return false;
            }

            return this.ToString().Equals(obj.ToString());
        }



        public static bool operator ==(Tags esquerda, Tags direita)
        {
            return esquerda.Equals(direita);
        }

        public static bool operator !=(Tags esquerda, Tags direita) =&amp;gt; !(esquerda == direita);


        public static implicit operator string(Tags t) =&amp;gt; t.ToString();

        public static implicit operator Tags(string s) =&amp;gt; new Tags(s);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alteramos e rodamos os testes e funcionou&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Dominio;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SharpTags;
using System.Numerics;

namespace TagStructureTest
{

    [TestClass]
    public class TagsTest
    {

        #region testes básicos passando na iteração1

        [TestMethod]
        public void TagsMustHaveCombinationOfUniqueTags()
        {

            Tags tags = new Tags();
            tags = tags.Add("tag1, tag2, tag3");
            tags = tags.Add("tag4, tag5, tag3");
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags.ToString());
        }

        [TestMethod]
        public void CanRemoveTags()
        {

            Tags tags = new Tags("tag1,tag2,tag3,tag4,tag5");
            tags = tags.Remove("tag1");
            tags = tags.Remove(new Tags("tag5"));
            Assert.AreEqual("tag2,tag3,tag4", tags.ToString());
        }


        [TestMethod]
        public void ProductMustHaveCombinationOfUniqueTags()
        {


            Produto prod = new Produto();
            prod.Tags = prod.Tags.Add("tag1, tag2, tag3");
            prod.Tags = prod.Tags.Add("tag4, tag5, tag3");
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", prod.Tags.ToString());
        }


        [TestMethod]
        public void CanRemoveTagsFromProduct()
        {

            Produto prod = new Produto();
            prod.Tags = prod.Tags.Add("tag1,tag2,tag3,tag4,tag5");
            prod.Tags = prod.Tags.Remove("tag1");
            prod.Tags = prod.Tags.Remove(new Tags("tag5"));
            Assert.AreEqual("tag2,tag3,tag4", prod.Tags.ToString());
        }

        #endregion


        #region testes novos criados na iteracao 1 (constructor, add, remove)
        [TestMethod]
        public void CanCreateTagsFromList()
        {
            Tags tags = new Tags(new List&amp;lt;string&amp;gt; { "tag1", "tag2", "tag3" });
            tags = tags.Add(new List&amp;lt;string&amp;gt; { "tag4", "tag5", "tag3" });
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags.ToString());
        }

        [TestMethod]
        public void CanCreateTagsFromString()
        {
            Tags tags = new Tags("tag1, tag2, tag3");
            tags = tags.Add("tag4, tag5, tag3");
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags.ToString());
        }

        [TestMethod]
        public void CanCreateTagsFromTags()
        {
            Tags tags = new Tags();
            tags = tags.Add(new Tags("tag1, tag2, tag3"));
            tags = tags.Add(new Tags("tag4, tag5, tag3"));
            Tags newTags = new Tags(tags);
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", newTags.ToString());
        }

        [TestMethod]
        public void CanAddTagsFromList()
        {
            Tags tags = new Tags();
            tags = tags.Add(new List&amp;lt;string&amp;gt; { "tag1", "tag2", "tag3" });
            tags = tags.Add(new List&amp;lt;string&amp;gt; { "tag4", "tag5", "tag3" });
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags.ToString());
        }

        [TestMethod]
        public void CanAddTagsFromString()
        {
            Tags tags = new Tags();
            tags = tags.Add("tag1, tag2, tag3");
            tags = tags.Add("tag4, tag5, tag3");
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags.ToString());
        }

        [TestMethod]
        public void CanAddTagsFromTags()
        {
            Tags tags = new Tags();
            tags = tags.Add(new Tags("tag1, tag2, tag3"));
            tags = tags.Add(new Tags("tag4, tag5, tag3"));
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags.ToString());
        }


        [TestMethod]
        public void CanRemoveTagsFromList()
        {
            Tags tags = new Tags("tag1,tag2,tag3,tag4,tag5");
            tags = tags.Remove(new List&amp;lt;string&amp;gt; { "tag1", "tag5"});
            Assert.AreEqual("tag2,tag3,tag4", tags.ToString());
        }

        [TestMethod]
        public void CanRemoveTagsFromString()
        {
            Tags tags = new Tags("tag1,tag2,tag3,tag4,tag5");
            tags = tags.Remove("tag1, tag5");
            Assert.AreEqual("tag2,tag3,tag4", tags.ToString());
        }

        [TestMethod]
        public void CanRemoveTagsFromTags()
        {
            Tags tags = new Tags("tag1,tag2,tag3,tag4,tag5");
            tags = tags.Remove(new Tags("tag1, tag5"));
            Assert.AreEqual("tag2,tag3,tag4", tags.ToString());
        }

        #endregion


        #region testes de igualdade 

        [TestMethod]
        public void TagsWithSameContentsShouldBeEquals()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Assert.AreEqual(tags1, tags2);
            Assert.IsTrue(tags1.Equals(tags2));
            Assert.IsTrue(tags1 == tags2); 
        }

        [TestMethod]
        public void SameTagsShouldBeEquals()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = tags1;
            Assert.AreEqual(tags1, tags2);
            Assert.IsTrue(tags1.Equals(tags2));
            Assert.IsTrue(tags1 == tags2); 
        }



        [TestMethod]
        public void TagsWithSameContentsShouldHaveSameHashcode()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Assert.AreEqual(tags1.GetHashCode(), tags2.GetHashCode());

        }

        [TestMethod]
        public void SameTagsShouldHaveSameHashcode()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = tags1;
            Assert.AreEqual(tags1.GetHashCode(), tags2.GetHashCode());
        }


        [TestMethod]
        public void TagsShouldBeEqualsToString()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Assert.IsTrue(tags1.Equals("tag1,tag2,tag3,tag4,tag5"));
        }

        [TestMethod]
        public void TagsShouldBeEqualsToStringUsingEqualityOperators()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Assert.IsTrue(tags1 == "tag1,tag2,tag3,tag4,tag5"); 
        }

        [TestMethod]
        public void ATagsVarShouldBeEqualsToASameContentString()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags1.ToString());
        }


        [TestMethod]
        public void EqualityOperatorSameVarTest()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = tags1;
            Assert.IsTrue(tags1 == tags2); 
        }

        [TestMethod]
        public void EqualityOperatorSameContentsTest()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = new Tags("tag1,tag2,tag3,tag4,tag5"); 
            Assert.IsTrue(tags1 == tags2); 
        }

        [TestMethod]
        public void InequalityOperatorSameVarTest()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = tags1;
            tags1 = tags2.Add("Teste");
            Assert.IsTrue(tags1 != tags2); 
        }

        [TestMethod]
        public void IneEqualityOperatorSameContentsTest()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = new Tags("tag1,tag2,tag3,tag4,tag6");
            Assert.IsTrue(tags1 != tags2); 
        }

        #endregion

    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas ainda falta testar bem esses dois implicit operator. &lt;br&gt;
Além disso, e se a gente transformasse cada operação de tags=tags.Add pra tags+= ? E tags.Remove pra tags-= ?&lt;br&gt;
Também precisamos fazer uma limpeza e dividir esses testes talvez em 5 arquivos: Testes de criação, Add, Remove e Gerais. O Arquivo de testes está ficando muito grande.&lt;br&gt;
Além disso está faltando um teste para o método GetTags (que retorna um array de string[]). Esse método não foi nem utilizado e não sei se GetTags é um nome apropriado pra ele. ToArray parece mais apropriado mas eu gostaria de evitar para não dar a impressão de que estamos implementando IEnumerable.&lt;br&gt;
Como nosso conteúdo é imutável, poderíamos guardar ele direto em uma string em vez de um ImmutableHashSet. Assim as consultas a ele, comparações e o ToString poderiam ficar mais performáticos.&lt;br&gt;
Mas se fizermos isso ficaria mais difícil implementar IEnumerable (para navegar entre as tags). Mas será que queremos fazer isso?&lt;br&gt;
Também precisamos decidir se vamos implementar IComparable e IEquatable.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>programming</category>
    </item>
    <item>
      <title>Struct para gerenciar Tags no C# - Parte 2</title>
      <dc:creator>Vitor Luiz Rubio</dc:creator>
      <pubDate>Fri, 10 Feb 2023 20:22:07 +0000</pubDate>
      <link>https://dev.to/vitorrubio/struct-para-tags-no-c-parte-2-3f05</link>
      <guid>https://dev.to/vitorrubio/struct-para-tags-no-c-parte-2-3f05</guid>
      <description>&lt;p&gt;Então estamos fazendo uma struct para trabalhar com Tags no C#. No primeiro artigo fizemos aquela primeira tentativa que não ficou muito boa e tinha apenas 4 testes. Agora vamos começar as melhorias, mais testes e a descrição do processo. &lt;/p&gt;




&lt;h3&gt;
  
  
  Iteração 1
&lt;/h3&gt;

&lt;p&gt;Mudamos a List interna para um HashSet porque o hashset já garante a unicidade das tags. Fizemos a renomeação de algumas vairáveis, e mais 9 testes. 3 de criação, 3 de add e 3 de remove.&lt;br&gt;
Deixamos a HashSet como readonly, para não mudarmos sua instância, mas mesmo assim ela (e todo o restante), é mutável. &lt;br&gt;
Deixamos a ordenação só para a saída ToString. &lt;br&gt;
Já podemos criar Tags a partir de strings usando um dos constructores ou convertê-las para strings, mas ainda não podemos simplesmente atribuir um objeto tags a uma string, ou uma string ao Tags. Também não temos o que é recomendável pela microsoft: Override de Equals, GetHashCode, etc.&lt;br&gt;
Igualdade entre tags com o mesmo conteúdo, como se fossem um record, Equal(), GetHashCode(), ==, nada disso está funcionando.&lt;/p&gt;
&lt;h3&gt;
  
  
  Iteração 2
&lt;/h3&gt;

&lt;p&gt;Adicionamos mais esses testes ao que foi feito na iteração 1 e todos passaram:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        [TestMethod]
        public void CanCreateTagsFromList()
        {
            Tags tags = new Tags(new List&amp;lt;string&amp;gt; { "tag1", "tag2", "tag3" });
            tags.AddTags(new List&amp;lt;string&amp;gt; { "tag4", "tag5", "tag3" });
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags.ToString());
        }

        [TestMethod]
        public void CanCreateTagsFromString()
        {
            Tags tags = new Tags("tag1, tag2, tag3");
            tags.AddTags("tag4, tag5, tag3");
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags.ToString());
        }

        [TestMethod]
        public void CanCreateTagsFromTags()
        {
            Tags tags = new Tags();
            tags.AddTags(new Tags("tag1, tag2, tag3"));
            tags.AddTags(new Tags("tag4, tag5, tag3"));
            Tags newTags = new Tags(tags);
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", newTags.ToString());
        }

        [TestMethod]
        public void CanAddTagsFromList()
        {
            Tags tags = new Tags();
            tags.AddTags(new List&amp;lt;string&amp;gt; { "tag1", "tag2", "tag3" });
            tags.AddTags(new List&amp;lt;string&amp;gt; { "tag4", "tag5", "tag3" });
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags.ToString());
        }

        [TestMethod]
        public void CanAddTagsFromString()
        {
            Tags tags = new Tags();
            tags.AddTags("tag1, tag2, tag3");
            tags.AddTags("tag4, tag5, tag3");
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags.ToString());
        }

        [TestMethod]
        public void CanAddTagsFromTags()
        {
            Tags tags = new Tags();
            tags.AddTags(new Tags("tag1, tag2, tag3"));
            tags.AddTags(new Tags("tag4, tag5, tag3"));
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags.ToString());
        }


        [TestMethod]
        public void CanRemoveTagsFromList()
        {
            Tags tags = new Tags("tag1,tag2,tag3,tag4,tag5");
            tags.RemoveTags(new List&amp;lt;string&amp;gt; { "tag1", "tag5"});
            Assert.AreEqual("tag2,tag3,tag4", tags.ToString());
        }

        [TestMethod]
        public void CanRemoveTagsFromString()
        {
            Tags tags = new Tags("tag1,tag2,tag3,tag4,tag5");
            tags.RemoveTags("tag1, tag5");
            Assert.AreEqual("tag2,tag3,tag4", tags.ToString());
        }

        [TestMethod]
        public void CanRemoveTagsFromTags()
        {
            Tags tags = new Tags("tag1,tag2,tag3,tag4,tag5");
            tags.RemoveTags(new Tags("tag1, tag5"));
            Assert.AreEqual("tag2,tag3,tag4", tags.ToString());
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Criamos testes que falham com certeza, alguns deles nem compilam por isso a parte que não compila está comentada para vermos os outros falharem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        [TestMethod]
        public void TagsWithSameContentsShouldBeEquals()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Assert.AreEqual(tags1, tags2);
            Assert.IsTrue(tags1.Equals(tags2));
            //Assert.IsTrue(tags1 == tags2); //não compila
        }

        [TestMethod]
        public void SameTagsShouldBeEquals()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = tags1;
            Assert.AreEqual(tags1, tags2);
            Assert.IsTrue(tags1.Equals(tags2));
            //Assert.IsTrue(tags1 == tags2); //não compila
        }



        [TestMethod]
        public void TagsWithSameContentsShouldHaveSameHashcodeEquals()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Assert.AreEqual(tags1.GetHashCode(), tags2.GetHashCode());

        }

        [TestMethod]
        public void SameTagsShouldHaveSameHashcodeEquals()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = tags1;
            Assert.AreEqual(tags1.GetHashCode(), tags2.GetHashCode());
        }


        [TestMethod]
        public void TagsShouldBeEqualsToString()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags1);
            Assert.IsTrue(tags1.Equals("tag1,tag2,tag3,tag4,tag5"));
            //Assert.IsTrue(tags1 == "tag1,tag2,tag3,tag4,tag5"); //não compila
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Iteração 3
&lt;/h3&gt;

&lt;p&gt;Os testes que criamos falharam porque não temos override do Equals, nem do GetHashCode, ou do operador == &lt;/p&gt;

&lt;p&gt;Também precisamos dar uma arrumada na casa, está tudo em um arquivo só porque fizemos no replit, mas está na hora de separar o projeto de teste do restante, a Tags para uma biblioteca e Product para uma suposta aplicação. &lt;br&gt;
Também estou passando o nome de todas as classes de domínio para o português para fins didáticos, e passando a Tags para uma biblioteca chamada SharpTags para simular uma biblioteca de terceiros (que é a maneira como outros a usariam)&lt;br&gt;
Criamos a biblioteca SharpTags&lt;br&gt;
Renomeamos TagStructureTest para SharpTagsTest&lt;br&gt;
Ficamos com a estrutura:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TagStructureTest
 ├─&amp;gt; Dominio
 │   ├── Dominio.csproj
 │   └── Produto.cs
 ├─&amp;gt; SharpTags
 │   ├── SharpTags.csproj
 │   └── Tags.cs
 ├─&amp;gt; TagStructureTest
 │   ├── TagsTest.cs
 │   └── TagStructureTest.csproj
 ├── TagStructureTest.sln
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adicionamos Override do Equals e do GetHashCode, que sempre devem ser implementados juntos. &lt;br&gt;
 O Override do GetHashCode eu simplesmente aproveitei que já temos um ToString e uso a hashcode que seria gerada para sua string. Não acredito que precisamos de algo melhor que isso por enquanto.&lt;br&gt;
 Já o Equals, primeiro ele verifica se dois objetos são o mesmo objeto/instância e retorna true, caso contrário verifica se o objeto sendo comparado é null e retorna false, por último ele vê se as duas strings resultantes são iguais, retornando esse resultado.&lt;br&gt;
 Não vamos entrar em detalhes sobre o GetHashCode, ele é um algritmo que gera um número inteiro único para um objeto e é usado para otimizar a performance ao armazenar esse objeto em hashes, como listas do tipo HashSet e Dictionary, fazendo com que sejam armazenados como se fosse em um vetor indexado numericamente (usando esse número gerado como índice) para evitar colisões e aumentar a performance em casoss de listas muito grandes. &lt;br&gt;
 A regra mais simples é: se dois objetos são iguais então seus hashes devem ser iguais. Se você fez o override de Equals é obrigado a fazer o override de GetHashCode.&lt;br&gt;
 Se você estivesse trabalhando com entidades aqui para serem persistidas em banco de dados com nHibernate ou EF, você faria o GetHashCode ser o próprio Id, e faria o Equals ser baseado no próprio Id também. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.object.equals?view=net-7.0"&gt;Definição e Guidelines para implementar Equals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.object.gethashcode?view=net-7.0"&gt;Implementação correta de GetHashCode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/visualstudio/ide/reference/generate-equals-gethashcode-methods?view=vs-2022"&gt;Usar o Visual Studio para gerar o Equals e o GetHashCode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-overriding-gethashcode"&gt;Discussão interessante sobre GetHashCode no Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Minha implementação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;         public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        public override bool Equals(object? obj)
        {
            if (object.ReferenceEquals(this, obj))
            {
                return true;
            }

            if (obj == null)
            {
                return false;
            }

            if ((!(obj is Tags)) &amp;amp;&amp;amp; (!(obj is string)))
            {
                return false;
            }

            return this.ToString().Equals(obj.ToString());
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quase todos passaram exceto os ainda comentados e o teste &lt;code&gt;Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags1);&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Iteração 4
&lt;/h3&gt;

&lt;p&gt;Para iniciar a iteração 4 vamos fazer o operator overloading dos sinais == e !=&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Operator overloading é um tipo de método que sobrecarrega ou motifica o comportamento de operadores. São úteis para quando precisamos que uma classe ou até uma struct se comporte como um tipo de dado especial até nos momentos em que usamos == ou !=. Por exemplo, nos objetos Produto os operadores == e != só comparam as referências e não o conteúdo. Nós alteraremos esse comportamento em Tags porque queremos que o conteúdo das tags seja considerado. &lt;br&gt;
Veja também&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/operator-overloading"&gt;Operator Overloading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#1410-operators"&gt;Operators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#1143-operator-overloading"&gt;Operator Overloading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/equality-operators#equality-operator-"&gt;== Operator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/equality-operators#inequality-operator-"&gt;!= Operator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Algumas mudanças foram feitas porque estamos tratando de igualdade e override de operators em um Value Type e não em um Reference Type. &lt;br&gt;
Isso torna algumas coisas mais simples embora outras precisem de mais cuidados. &lt;br&gt;
O trecho de código abaixo podemos tirar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            if (object.ReferenceEquals(this, obj))
            {
                return true;
            }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com Value Types não precisamos lidar com Reference Equals nem com nulidade.&lt;/p&gt;

&lt;p&gt;Os operadores == e != implementados. Veja que o != é facil, porque ele é a negação do ==.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        public static bool operator ==(Tags esquerda, Tags direita)
        {
            return esquerda.Equals(direita);
        }

        public static bool operator !=(Tags esquerda, Tags direita) =&amp;gt; !(esquerda == direita);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora podemos descomentar as linhas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Assert.IsTrue(tags1 == tags2);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;mas a &lt;code&gt;Assert.IsTrue(tags1 == "tag1,tag2,tag3,tag4,tag5");&lt;/code&gt; ainda não compila, segura ela comentada.&lt;br&gt;
Podemos colocar esses métodos em outros testes também. &lt;br&gt;
O teste TagsShouldBeEqualsToString continua não passando, porque compara com string, ainda não implementamos isso. &lt;/p&gt;

&lt;p&gt;Acrescentei  mais 4 testes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        [TestMethod]
        public void EqualityOperatorSameVarTest()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = tags1;
            Assert.IsTrue(tags1 == tags2); 
        }

        [TestMethod]
        public void EqualityOperatorSameContentsTest()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = new Tags("tag1,tag2,tag3,tag4,tag5"); 
            Assert.IsTrue(tags1 == tags2); 
        }

        [TestMethod]
        public void InequalityOperatorSameVarTest()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = tags1;
            tags2.AddTags("Teste");
            Assert.IsTrue(tags1 != tags2); 
        }

        [TestMethod]
        public void IneEqualityOperatorSameContentsTest()
        {
            Tags tags1 = new Tags("tag1,tag2,tag3,tag4,tag5");
            Tags tags2 = new Tags("tag1,tag2,tag3,tag4,tag6");
            Assert.IsTrue(tags1 != tags2); 
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E não estranhamente o InequalityOperatorSameVarTest não passou. Porque? Porque ambos estão compartilhando a mesma  HashSet _taglist, por referência, que está sendo mudada pelo método AddTags. Podemos mudar esse método para criar uma nova, mas isso fará novamente com que outros testes, principalmente de retorno de funções e getters retornando Tags (como no caso de Produto), falhem.&lt;/p&gt;

&lt;p&gt;A solução é transformar a classe em imutável de vez. Isso vai envolver bastante energia por isso deixaremos para a iteração 5.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>programming</category>
    </item>
    <item>
      <title>Struct para gerenciar Tags no C#</title>
      <dc:creator>Vitor Luiz Rubio</dc:creator>
      <pubDate>Fri, 10 Feb 2023 16:24:35 +0000</pubDate>
      <link>https://dev.to/vitorrubio/struct-para-gerenciar-tags-no-c-1590</link>
      <guid>https://dev.to/vitorrubio/struct-para-gerenciar-tags-no-c-1590</guid>
      <description>&lt;h1&gt;
  
  
  Struct para gerenciar Tags no C
&lt;/h1&gt;

&lt;p&gt;Ontem eu tentei fazer um tipo no C# para servir como tags para os produtos / serviços da minha empresa, e que pudesse ser usada em qualquer classe que precisasse te tags e que pudesse ser lido/gravado no banco de dados usando EF como se fosse uma string normal.&lt;br&gt;
Queria que atendesse aos seguintes requisitos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;mantivesse uma lista de strings únicas e em minúscula&lt;/li&gt;
&lt;li&gt;fosse conversível para string (com o ToString mas implementamos também overload de conversão implícita) retornando a lista de tags únicas em minúsculas, separadas por vírgula e ordenadas.&lt;/li&gt;
&lt;li&gt;fosse conversível DE string&lt;/li&gt;
&lt;li&gt;Se comportasse como string de todas as formas&lt;/li&gt;
&lt;li&gt;Se parecesse com um tipo nativo do .Net&lt;/li&gt;
&lt;li&gt;Fosse no meu domínio um value object&lt;/li&gt;
&lt;li&gt;Fosse compatível com o EF&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Um disclaimer aqui: essa classe não vai contar as tags para medir relevância ou fazer tag cloud. &lt;/p&gt;

&lt;p&gt;Quero deixar claro que meu primeiro código está muito longe de estar correto, na verdade está um lixo, não o use. Tem várias coisas absolutamente erradas e é um bom exemplo de como mesmo devs experientes podem cometer grandes erros em coisas simples. &lt;/p&gt;

&lt;p&gt;O meu primeiro código eu fiz a classe Product, a struct Tags (gostaria de insistir em struct por enquanto) e 4 testes unitários. &lt;br&gt;
Dois testes falharam e dois passaram, e eu fiquei intrigado com o motivo que levou dois deles a falharem e propus o desafio ontem no &lt;a href="https://replit.com/"&gt;replit&lt;/a&gt;&lt;br&gt;
Vou postar aqui o código errado, os primeiros acertos que fiz e o &lt;a href="https://github.com/vitorrubio/TagStructure"&gt;repositório no github&lt;/a&gt; com o código correto. No repositório do github teremos várias branches com os nomes iteracao1, iteracao2 e assim por diante para você poder ver a evolução no código com o passar do tempo. Na Main/Master teremos a última versão do código. &lt;/p&gt;

&lt;p&gt;Abaixo o código da iteração ZERO.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;///ATENÇÃO: CÓDIGO REDONDAMENTE ERRADO PARA FINS DIDÁTICOS
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;

namespace TagStructureTest
{

    public struct Tags
    {
        private List&amp;lt;string&amp;gt; tags = new List&amp;lt;string&amp;gt;();

        public Tags() { }
        public Tags(params string[] t)
        {
            if (tags != null)
            {
                this.tags.AddRange(t.Where(x =&amp;gt; !string.IsNullOrWhiteSpace(x)).SelectMany(x =&amp;gt; x!.Split(",")).Select(x =&amp;gt; x.Trim().ToLower()).OrderBy(x =&amp;gt; x).Distinct());
            }
        }

        public override string ToString()
        {
            return string.Join(",", tags.Select(x =&amp;gt; x.Trim().ToLower()).OrderBy(x =&amp;gt; x).Distinct());
        }

        public void AddTags(params string[] t)
        {
            var tagsToAdd = t.Where(x =&amp;gt; !string.IsNullOrWhiteSpace(x)).SelectMany(x =&amp;gt; x!.Split(",")).Select(x =&amp;gt; x.Trim().ToLower()).Distinct().ToList();
            this.tags = this.tags.Union(tagsToAdd).Select(x =&amp;gt; x.Trim().ToLower()).OrderBy(x =&amp;gt; x).Distinct().ToList();


        }

        public void AddTags(Tags t)
        {
            this.AddTags(t.GetTags());
        }


        public void RemoveTags(params string[] t)
        {
            var tagsToRemove = t.Where(x =&amp;gt; !string.IsNullOrWhiteSpace(x)).SelectMany(x =&amp;gt; x!.Split(",")).Select(x =&amp;gt; x.Trim().ToLower()).Distinct();

          tags = tags.Except(tagsToRemove).Select(x =&amp;gt; x.Trim().ToLower()).OrderBy(x =&amp;gt; x).Distinct().ToList();
        }

        public void RemoveTags(Tags tags)
        {
            this.RemoveTags(tags.GetTags());
        }

        public string[] GetTags()
        {
            return this.tags.Select(x =&amp;gt; x.Trim().ToLower()).OrderBy(x =&amp;gt; x).Distinct().ToArray();
        }
    }


    public class Product
    {
        public Product()
        {
            Name = string.Empty;
            Tags = new Tags();
        }


        public string Name { get; set; }
        public Tags Tags { get; init; }
    }



    [TestClass]
    public class TagsTest
    {
        [TestMethod]
        public void TagsMustHaveCombinationOfUniqueTags()
        {

            Tags tags = new Tags();
            tags.AddTags("tag1, tag2, tag3");
            tags.AddTags("tag4, tag5, tag3");
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", tags.ToString());
        }


        [TestMethod]
        public void CanRemoveTags()
        {

            Tags tags = new Tags("tag1,tag2,tag3,tag4,tag5");
            tags.RemoveTags("tag1");
            tags.RemoveTags(new Tags("tag5"));
            Assert.AreEqual("tag2,tag3,tag4", tags.ToString());


        }


        [TestMethod]
        public void ProductMustHaveCombinationOfUniqueTags()
        {


            Product prod = new Product();
            prod.Tags.AddTags("tag1, tag2, tag3");
            prod.Tags.AddTags("tag4, tag5, tag3");
            Assert.AreEqual("tag1,tag2,tag3,tag4,tag5", prod.Tags.ToString());



        }


        [TestMethod]
        public void CanRemoveTagsFromProduct()
        {

            Product prod = new Product { Tags = new Tags("tag1,tag2,tag3,tag4,tag5") };
            prod.Tags.RemoveTags("tag1");
            prod.Tags.RemoveTags(new Tags("tag5"));
            Assert.AreEqual("tag2,tag3,tag4", prod.Tags.ToString());


        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como eu disse, o código está errado. O primeiro erro é conceitual:&lt;br&gt;
Faltou a imutabilidade. Depois de criada uma Tags eu não deveria poder alterar seu estado ou conteúdo através de métodos, mas apenas criando um novo. &lt;br&gt;
Pense nos exemplos string e DateTime do .net: &lt;br&gt;
Quando você tem string &lt;code&gt;nome = "vitor"&lt;/code&gt; não existe um nome.Append(" Rubio"), mas você tem que criar uma nova string concatenando as duas, o jeito mais simples é &lt;code&gt;nome = nome + " Rubio"&lt;/code&gt;. Note que mesmo colocando na mesma variável, criamos uma nova string.&lt;br&gt;
A mesma coisa com datas. Se você tem &lt;code&gt;DateTime amanha = DateTime.Today&lt;/code&gt;e faz &lt;code&gt;amanha.AddDays(1)&lt;/code&gt; a variável amanha continua tendo o valor de hoje. Isso porque seu estado não é alterado. O método AddDays que retorna o dia de amanhã. Então o correto seria &lt;code&gt;amanha = amanha.AddDays(1)&lt;/code&gt; ou &lt;code&gt;DateTime amanha = DateTime.Today.AddDays(1)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pensando nisso a struct Tags deveria ser totalmente repensada. &lt;/p&gt;

&lt;p&gt;Mas vamos fingir que está tudo bem, vamos usar ela como está primeiro, sem ser imutável e fazer apenas os testes rodarem.&lt;/p&gt;
&lt;h2&gt;
  
  
  Primeiro erro:
&lt;/h2&gt;

&lt;p&gt;Meu primeiro erro foi fazer com que Product tivesse uma auto property com autoproperties usando getter &lt;code&gt;public Tags Tags { get; init; }&lt;/code&gt;,  mesmo se fosse somente leitura. &lt;br&gt;
Isso porque um getter é somente uma sintax sugar para um método Getxxx (GetTags por exemplo). E como Tags é uma struct e structs são value types ... bem, value types não são passados por referência no retorno de funções, eles são copiados. Então toda vez que eu acesso a propriedade Tags de Product eu estou com um Tags novíssimo.&lt;br&gt;
Isso não seria tão problemático se pelo menos mantivéssemos o Tags apontando para a mesma List tags. Mas este não é o caso. Toda vez que fazíamos um AddTags ou RemoveTags criávamos uma nova lista. Isso fazia com que dentro do objeto Tags antigo uma nova lista com nosso conteúdo fosse criada enquanto que o Tags novo que retornou do getter da property está ainda apontando para a List tags vazia. &lt;br&gt;
Também ficou confuso com essa nomenclatura e toda essa chamada de métodos pra limpar, ordenar e garantir a unicidade. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mais sobre &lt;a href="https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/data-types/value-types-and-reference-types"&gt;Structure Types e Value Types&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Segundo erro:
&lt;/h2&gt;

&lt;p&gt;No post origina do  &lt;a href="https://replit.com/"&gt;replit&lt;/a&gt;, nos métodos AddTags da linha 26 e RemoveTags da linha 40 eu criava uma nova instancia List tags. (com new ou ToList()).&lt;br&gt;
Esse é um erro que merece uma atenção especial: ao misturar reference types com value types dentro de structs garanta que seus membros sejam readonly, imutáveis e que não sejam criadas novas instâncias neles. Até em classes esses cuidados devem ser tomados, mas em structs eles se fazem muito mais importantes. &lt;/p&gt;

&lt;p&gt;Então o código do AddTags poderia ser assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                var current = tags.ToArray();
                var tagsToAdd = t.Where(x =&amp;gt; !string.IsNullOrWhiteSpace(x)).SelectMany(x =&amp;gt; x!.Split(",")).Select(x =&amp;gt; x.Trim().ToLower()).Distinct().ToArray();
                tags.Clear();
                tags.AddRange(current.Union(tagsToAdd).Select(x =&amp;gt; x.Trim().ToLower()).Distinct().ToList());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o método RemoveTags poderia ser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                var tagsToRemove = t.Where(x =&amp;gt; !string.IsNullOrWhiteSpace(x)).SelectMany(x =&amp;gt; x!.Split(",")).Select(x =&amp;gt; x.Trim().ToLower()).Distinct();
                _taglist.RemoveAll(x =&amp;gt; tagsToRemove.Contains(x));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas ainda sim estamos lidando com tags mutáveis. Todo esse aparato deveria ser imutável. &lt;/p&gt;

&lt;h2&gt;
  
  
  Terceiro erro:
&lt;/h2&gt;

&lt;p&gt;Seria bom que a List tags fosse somente leitura, e isso já seria um passo na direção correta&lt;/p&gt;

&lt;h2&gt;
  
  
  Quarto erro:
&lt;/h2&gt;

&lt;p&gt;Eu não deveria alterar as tags de um produto com &lt;code&gt;product.Tags.AddTags&lt;/code&gt; .... mas sim através de um método &lt;code&gt;product.AddTags&lt;/code&gt;, essa é a &lt;a href="https://pt.wikipedia.org/wiki/Lei_de_Demeter"&gt;lei de Demeter&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;É isso&lt;/p&gt;

&lt;p&gt;Links e referências:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/data-types/value-types-and-reference-types"&gt;Structure Types e Value Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vitorrubio/TagStructure"&gt;repositório no github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://replit.com/"&gt;post original no replit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pt.wikipedia.org/wiki/Lei_de_Demeter"&gt;lei de Demeter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>SOLID Sucinto</title>
      <dc:creator>Vitor Luiz Rubio</dc:creator>
      <pubDate>Fri, 10 Feb 2023 12:47:37 +0000</pubDate>
      <link>https://dev.to/vitorrubio/solid-sucinto-mp6</link>
      <guid>https://dev.to/vitorrubio/solid-sucinto-mp6</guid>
      <description>&lt;h2&gt;
  
  
  SOLID
&lt;/h2&gt;

&lt;p&gt;S - princípio da responsabilidade única&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;cada classe só pode ter uma responsabilidade.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;O - princípio do “aberto/fechado”&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;uma classe deve ser fechada a mudanças e aberta a extensão. Isso quer dizer que você deve conseguir modificar ou evoluir o comportamento do sistema sem modificar as classes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;L - princípio da substituição de Liskov&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;um objeto deve poder ser substituído por qualquer um de seus descendentes ou derivados sem comprometer o funcionamento do sistema.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;I - princípio da segregação de interfaces&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;programe orientado à interfaces e não a implementação. Faça classes que te permita fazer manutenção modificando apenas os membros privados sem mexer nos públicos ou na interfaces.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;D - injeção de dependência&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;outros objetos que uma classe ou método precisa devem ser criados fora dela e passados como parâmetro pro construtor, ou por um método, e nunca criado dentro dela, pra evitar acoplamento (cite frameworks que você conheça, como o Simple Injector no C#). Outro conceito relacionado a isso é inversão de responsabilidade (IoC - Inversion of Concenrs em inglês).&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>solid</category>
      <category>cleancode</category>
      <category>arquitetura</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Lista de Colinhas para Devs</title>
      <dc:creator>Vitor Luiz Rubio</dc:creator>
      <pubDate>Fri, 10 Feb 2023 12:39:25 +0000</pubDate>
      <link>https://dev.to/vitorrubio/lista-de-colinhas-para-devs-2j8c</link>
      <guid>https://dev.to/vitorrubio/lista-de-colinhas-para-devs-2j8c</guid>
      <description>&lt;p&gt;Inspirado naqueles PDF de cheat sheet (colinha) que geralmente tem de Markdown, Git ou outras tecnologias eu resolvi fazer uma série de colinhas para devs consultarem no trabalho ou estudar para entrevistas. &lt;/p&gt;

&lt;p&gt;Algumas direções que eu quero tomar nesse projetinho: &lt;br&gt;
1 - tem que ser sucinto, o mais resumido possível&lt;br&gt;
2 - tem que ser em pt-BR pra ajudar os novatos e quem não tem inglês&lt;/p&gt;

&lt;p&gt;Por favor me ajudem a expandir e melhorar as colinhas. Se encontrarem algum erro ou desinformação. Erro de português ou se precisar resumir ainda mais. &lt;/p&gt;

&lt;p&gt;Quem quiser dar sugestões de colinhas, manda aí a sugestão. &lt;/p&gt;

&lt;p&gt;Quem precisar de uma tradução de um artigo ou manual em inglês manda aí que eu dou um jeito. &lt;/p&gt;

&lt;p&gt;Com o tempo eu vou melhorando a colinha e jogando links novos aqui. Por enquanto temos esses abaixo: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/vitorrubio/solid-sucinto-mp6"&gt;SOLID Sucinto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>remote</category>
      <category>workplace</category>
      <category>career</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
