Há várias coisas acontecendo nesta questão ...
É possível para uma estrutura implementar uma interface, mas existem preocupações que surgem com a conversão, mutabilidade e desempenho. Veja esta postagem para mais detalhes: https://docs.microsoft.com/en-us/archive/blogs/abhinaba/c-structs-and-interface
Em geral, structs devem ser usados para objetos que têm semântica de tipo de valor. Ao implementar uma interface em uma estrutura, você pode ter problemas de boxing, pois a estrutura é lançada para frente e para trás entre a estrutura e a interface. Como resultado do boxing, as operações que alteram o estado interno da estrutura podem não se comportar adequadamente.
IComparable<T>
eIEquatable<T>
. Armazenar uma estruturaFoo
em uma variável de tipoIComparable<Foo>
exigiria encaixotamento, mas se um tipo genéricoT
for restrito aIComparable<T>
um, podemos compará-lo a outroT
sem precisar encaixar nenhum dos dois e sem precisar saber nada sobre aT
não ser que ele implementa a restrição. Esse comportamento vantajoso só é possível pela capacidade dos structs de implementar interfaces. Dito isto ...Uma vez que ninguém mais forneceu explicitamente esta resposta, acrescentarei o seguinte:
Implementar uma interface em uma estrutura não tem consequências negativas de qualquer espécie.
Qualquer variável do tipo de interface usada para conter uma estrutura resultará em um valor em caixa dessa estrutura sendo usada. Se a estrutura for imutável (uma coisa boa), isso é, na pior das hipóteses, um problema de desempenho, a menos que você:
Ambos seriam improváveis, em vez disso, você provavelmente fará um dos seguintes:
Genéricos
Talvez muitos motivos razoáveis para structs implementando interfaces sejam para que eles possam ser usados em um contexto genérico com restrições . Quando usada desta forma, a variável assim:
new()
ouclass
seja usada.Então this.a NÃO é uma referência de interface, portanto, não causa uma caixa com o que quer que seja colocado nela. Além disso, quando o compilador c # compila as classes genéricas e precisa inserir invocações dos métodos de instância definidos nas instâncias do parâmetro Type T, ele pode usar o opcode restrito :
Isso evita o boxing e como o tipo de valor está implementando a interface é necessário implementar o método, portanto, nenhum boxing ocorrerá. No exemplo acima, a
Equals()
invocação é feita sem nenhuma caixa neste.a 1 .APIs de baixa fricção
A maioria dos structs deve ter uma semântica de tipo primitivo, em que valores idênticos bit a bit são considerados iguais 2 . O tempo de execução fornecerá esse comportamento implícito,
Equals()
mas isso pode ser lento. Além disso, essa igualdade implícita não é exposta como uma implementação deIEquatable<T>
e, portanto, evita que as estruturas sejam usadas facilmente como chaves para Dicionários, a menos que elas mesmas as implementem explicitamente. Portanto, é comum que muitos tipos de estruturas públicas declarem que implementamIEquatable<T>
(ondeT
estão eles próprios) para tornar isso mais fácil e com melhor desempenho, bem como consistentes com o comportamento de muitos tipos de valor existentes no CLR BCL.Todas as primitivas no BCL implementam no mínimo:
IComparable
IConvertible
IComparable<T>
IEquatable<T>
(E assimIEquatable
)Muitos também implementam
IFormattable
, além de muitos dos tipos de valor definidos pelo sistema, como DateTime, TimeSpan e Guid, implementam muitos ou todos eles também. Se você estiver implementando um tipo similarmente 'amplamente útil', como uma estrutura de número complexo ou alguns valores textuais de largura fixa, a implementação de muitas dessas interfaces comuns (corretamente) tornará sua estrutura mais útil e utilizável.Exclusões
Obviamente, se a interface implica fortemente em mutabilidade (como
ICollection
), implementá-la é uma má ideia, pois significaria que você tornou a estrutura mutável (levando aos tipos de erros já descritos onde as modificações ocorrem no valor em caixa em vez do original ) ou você confunde os usuários ignorando as implicações dos métodos comoAdd()
ou lançando exceções.Muitas interfaces NÃO implicam em mutabilidade (como
IFormattable
) e servem como a forma idiomática de expor certas funcionalidades de maneira consistente. Freqüentemente, o usuário da estrutura não se preocupa com qualquer sobrecarga de boxing para tal comportamento.Resumo
Quando feito de maneira sensata, em tipos de valor imutáveis, a implementação de interfaces úteis é uma boa ideia
Notas:
1: Observe que o compilador pode usar isso ao invocar métodos virtuais em variáveis que são conhecidas por serem de um tipo de estrutura específico, mas nas quais é necessário invocar um método virtual. Por exemplo:
O enumerador retornado pela Lista é uma estrutura, uma otimização para evitar uma alocação ao enumerar a lista (com algumas consequências interessantes ). No entanto, a semântica de foreach especifica que, se o enumerador implementar,
IDisposable
entãoDispose()
será chamado assim que a iteração for concluída. Obviamente, isso ocorrer por meio de uma chamada em caixa eliminaria qualquer benefício de o enumerador ser uma estrutura (na verdade, seria pior). Pior, se a chamada dispose modifica o estado do enumerador de alguma forma, isso aconteceria na instância em caixa e muitos bugs sutis podem ser introduzidos em casos complexos. Portanto, o IL emitido neste tipo de situação é:Portanto, a implementação de IDisposable não causa nenhum problema de desempenho e o aspecto mutável (lamentável) do enumerador é preservado caso o método Dispose realmente faça alguma coisa!
2: double e float são exceções a esta regra onde os valores NaN não são considerados iguais.
fonte
struct
para uminterface
.Em alguns casos, pode ser bom para uma estrutura implementar uma interface (se nunca foi útil, é duvidoso que os criadores de .net a teriam fornecido). Se uma estrutura implementa uma interface somente leitura como
IEquatable<T>
, armazenar a estrutura em um local de armazenamento (variável, parâmetro, elemento de matriz, etc.) do tipoIEquatable<T>
exigirá que seja encaixotada (cada tipo de estrutura na verdade define dois tipos de coisas: um armazenamento tipo de localização que se comporta como um tipo de valor e um tipo de objeto heap que se comporta como um tipo de classe; o primeiro é implicitamente conversível para o segundo - "boxing" - e o segundo pode ser convertido para o primeiro por meio de conversão explícita - "unboxing"). É possível explorar a implementação de uma interface de uma estrutura sem boxing, entretanto, usando o que são chamados de genéricos restritos.Por exemplo, se alguém tivesse um método
CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T>
, tal método poderia chamarthing1.Compare(thing2)
sem a necessidade de boxthing1
outhing2
. Sething1
acontecer de ser, por exemplo, umInt32
, o tempo de execução saberá disso quando gerar o código paraCompareTwoThings<Int32>(Int32 thing1, Int32 thing2)
. Como ele saberá o tipo exato da coisa que hospeda o método e da coisa que está sendo passada como parâmetro, não precisará encaixotar nenhum deles.O maior problema com structs que implementam interfaces é que um struct que é armazenado em um local do tipo de interface
Object
, ouValueType
(ao contrário de um local de seu próprio tipo) se comportará como um objeto de classe. Para interfaces somente leitura, isso geralmente não é um problema, mas para uma interface mutante comoIEnumerator<T>
ela pode produzir algumas semânticas estranhas.Considere, por exemplo, o seguinte código:
A instrução marcada nº 1 irá preparar
enumerator1
para ler o primeiro elemento. O estado desse enumerador será copiado paraenumerator2
. A instrução marcada nº 2 avançará essa cópia para ler o segundo elemento, mas não afetaráenumerator1
. O estado desse segundo enumerador será então copiado paraenumerator3
, que será avançado pela declaração marcada # 3. Então, comoenumerator3
eenumerator4
são tipos de referência, um REFERENCE toenumerator3
será copiado paraenumerator4
, portanto, a instrução marcada avançará efetivamente ambosenumerator3
eenumerator4
.Algumas pessoas tentam fingir que tipos de valor e tipos de referência são ambos tipos de
Object
, mas isso não é realmente verdade. Tipos de valor real são conversíveis emObject
, mas não são instâncias dele. Uma instânciaList<String>.Enumerator
armazenada em um local desse tipo é um tipo de valor e se comporta como um tipo de valor; copiá-lo para um local do tipoIEnumerator<String>
irá convertê-lo em um tipo de referência e ele se comportará como um tipo de referência . O último é uma espécie deObject
, mas o primeiro não é.A propósito, mais algumas observações: (1) Em geral, os tipos de classes mutáveis devem ter seus
Equals
métodos para testar a igualdade de referência, mas não há uma maneira decente de uma estrutura em caixa fazer isso; (2) apesar do nome,ValueType
é um tipo de classe, não um tipo de valor; todos os tipos derivados deSystem.Enum
são tipos de valor, assim como todos os tipos derivados de,ValueType
com exceção deSystem.Enum
, mas ambosValueType
eSystem.Enum
são tipos de classe.fonte
As estruturas são implementadas como tipos de valor e as classes são tipos de referência. Se você tiver uma variável do tipo Foo e armazenar uma instância de Fubar nela, ela irá "encaixá-la" em um tipo de referência, anulando assim a vantagem de usar uma estrutura em primeiro lugar.
A única razão que vejo para usar uma estrutura em vez de uma classe é porque será um tipo de valor e não um tipo de referência, mas a estrutura não pode herdar de uma classe. Se você fizer com que a estrutura herde uma interface e passar interfaces, você perderá a natureza do tipo de valor da estrutura. Pode muito bem torná-lo uma classe se você precisar de interfaces.
fonte
(Bem, não tenho nada de importante a acrescentar, mas ainda não tenho habilidade de edição, então aqui vai ...)
Perfeitamente seguro. Nada ilegal com a implementação de interfaces em structs. No entanto, você deve questionar por que deseja fazer isso.
No entanto, obter uma referência de interface para uma estrutura irá encaixá- la no BOX . Portanto, penalidade de desempenho e assim por diante.
O único cenário válido que posso pensar agora é ilustrado em meu post aqui . Quando você quiser modificar o estado de um struct armazenado em uma coleção, terá que fazer isso por meio de uma interface adicional exposta no struct.
fonte
Int32
para um método que aceita um tipo genéricoT:IComparable<Int32>
(que pode ser um parâmetro de tipo genérico do método ou a classe do método), esse método será capaz de usar oCompare
método no objeto passado sem encaixá-lo.Acho que o problema é que isso causa boxe porque structs são tipos de valor, então há uma pequena penalidade de desempenho.
Este link sugere que pode haver outros problemas com ele ...
http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
fonte
Não há consequências para uma estrutura que implementa uma interface. Por exemplo, as estruturas de sistema integradas implementam interfaces como
IComparable
eIFormattable
.fonte
Há muito pouca razão para um tipo de valor implementar uma interface. Visto que você não pode criar uma subclasse de um tipo de valor, você sempre pode se referir a ele como seu tipo concreto.
A menos, é claro, que você tenha vários structs, todos implementando a mesma interface, pode ser marginalmente útil, mas nesse ponto eu recomendo usar uma classe e fazê-lo direito.
Claro, ao implementar uma interface, você está encaixotando a estrutura, então ela agora fica no heap e você não será mais capaz de passá-la por valor ... Isso realmente reforça minha opinião de que você deve apenas usar uma classe nesta situação.
fonte
IComparable
para encaixotar o valor. Simplesmente chamando um método que esperaIComparable
com um tipo de valor que o implementa, você irá encaixotar implicitamente o tipo de valor.IComparable<T>
sejam chamados em estruturas do tipoT
sem encaixe.Structs são como classes que vivem na pilha. Não vejo razão para que sejam "inseguros".
fonte