É permitido usar a implementação explícita da interface para ocultar membros em c #?


14

Eu entendo como trabalhar com interfaces e implementação explícita de interfaces em C #, mas fiquei pensando se é considerado uma forma incorreta ocultar certos membros que não seriam usados ​​com freqüência. Por exemplo:

public interface IMyInterface
{
    int SomeValue { get; set; }
    int AnotherValue { get; set; }
    bool SomeFlag { get; set; }

    event EventHandler SomeValueChanged;
    event EventHandler AnotherValueChanged;
    event EventHandler SomeFlagChanged;
}

Sei antecipadamente que os eventos, embora úteis, raramente seriam usados. Portanto, pensei que faria sentido escondê-los de uma classe de implementação por meio de uma implementação explícita para evitar a confusão do IntelliSense.


3
Se você estiver referenciando sua instância por meio da interface, ela não os ocultará, apenas ocultará se sua referência for a um tipo concreto.
21413 Andy

1
Eu trabalhei com um cara que fazia isso regularmente. Isso me deixou absolutamente louco.
Joel Etherton

... e comece a usar a agregação.
Mikalai

Respostas:


39

Sim, geralmente é uma forma ruim.

Portanto, pensei que faria sentido escondê-los de uma classe de implementação por meio de uma implementação explícita para evitar a confusão do IntelliSense.

Se o seu IntelliSense estiver muito confuso, sua classe é muito grande. Se sua turma é muito grande, provavelmente está fazendo muitas coisas e / ou mal projetada.

Corrija seu problema, não seu sintoma.


É apropriado usá-lo para remover avisos do compilador sobre membros não utilizados?
Gusdor 28/10

11
"Corrija seu problema, não seu sintoma." +1
FreeAsInBeer

11

Use o recurso para o que foi planejado. Reduzir a desorganização do espaço para nome não é um deles.

Razões legítimas não são sobre "esconder" ou organização, são para resolver ambigüidades e se especializar. Se você implementar duas interfaces que definem "Foo", a semântica para Foo pode ser diferente. Realmente não é uma maneira melhor de resolver isso, exceto para interfaces explícitas. A alternativa é proibir a implementação de interfaces sobrepostas ou declarar que as interfaces não podem associar semântica (que é apenas uma coisa conceitual). Ambos são más alternativas. Às vezes, a praticidade supera a elegância.

Alguns autores / especialistas consideram esse recurso um recurso (os métodos não fazem parte do modelo de objeto do tipo). Mas, como todos os recursos, em algum lugar, alguém precisa.

Novamente, eu não o usaria para esconder ou organizar. Existem maneiras de esconder membros do intellisense.

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]

"Existem autores / especialistas proeminentes que consideram uma amostra de um recurso." Quem é? Qual é a alternativa proposta? Há pelo menos um problema (ou seja, especialização de métodos de interface em uma subinterface / classe de implementação) que exigiria que outro recurso fosse adicionado ao C # para resolvê-lo na ausência de implementação explícita (consulte ADO.NET e coleções genéricas) .
Jhominal

@jhominal - CLR via C # de Jeffrey Richter usa especificamente a palavra kludge. C ++ se dá bem sem ele, o programador deve se referir explicitamente à implementação do método base para desambiguar ou usar uma conversão. Eu já mencionei que as alternativas não são muito atraentes. Mas é um aspecto de herança e interfaces únicas, não de OOP em geral.
Codenheim

Você também pode ter mencionado que o Java também não possui implementação explícita, apesar de ter o mesmo design de "herança única da implementação + interfaces". No entanto, em Java, o tipo de retorno de um método substituído pode ser especializado, evitando parte da necessidade de implementação explícita. (Em C #, uma decisão de design diferente foi tomada, provavelmente em parte devido à inclusão de propriedades).
Jhominal

@jhominal - Com certeza, quando tiver alguns minutos depois, atualizarei. Obrigado.
Codenheim

Ocultar pode ser inteiramente apropriado em situações em que uma classe pode implementar um método de interface de uma maneira que esteja em conformidade com o contrato de interface, mas não seja útil . Por exemplo, embora eu não IDisposable.Disposegoste da ideia de classes em que algo faz, mas receba um nome diferente Close, consideraria perfeitamente razoável para uma classe cujo contrato se isenta expressamente de declarar que o código pode criar e abandonar instâncias sem Disposeprecisar usar a implementação explícita da interface para defina um IDisposable.Disposemétodo que não faz nada.
Supercat

5

Eu posso ser um sinal de código confuso, mas às vezes o mundo real é confuso. Um exemplo do .NET framework é o ReadOnlyColection, que oculta o setter mutante [], Add and Set.

O IE ReadOnlyCollection implementa IList <T>. Veja também minha pergunta para SO há vários anos atrás, quando refleti sobre isso.


Bem, é a minha própria interface que vou usar também, então não faz sentido fazer errado desde o início.
Kyle Baran

2
Eu não diria que oculta o criador de mutações, mas sim que ele não o fornece. Um exemplo melhor seria ConcurrentDictionary, que implementa vários membros de IDictionary<T>forma explícita para que os consumidores de ConcurrentDictionaryA) não os chamem diretamente e B) possam usar métodos que exigem uma implementação, IDictionary<T>se absolutamente necessário. Por exemplo, os usuários de ConcurrentDictionary<T> devem ligar em TryAddvez de Addevitar a necessidade de exceções desnecessárias.
Brian

Obviamente, implementar Addexplicitamente também reduz a desordem. TryAddpode fazer qualquer coisa Add( Addé implementado como uma chamada para TryAdd, portanto, fornecer ambos seria redundante). No entanto, TryAddnecessariamente tem um nome / assinatura diferente, portanto, não é possível implementar IDictionarysem essa redundância. A implementação explícita resolve esse problema corretamente.
28714 Brian

Bem, do ponto de vista do consumidor, os métodos estão ocultos.
Esben Skov Pedersen 28/10

1
Você escreve sobre IReadonlyCollection <T>, mas vincula a ReadOnlyCollection <T>. O primeiro de uma interface, o segundo é uma classe. IReadonlyCollection <T> não implementa IList <T>, embora ReadonlyCollection <T> faça.
Jørgen Fogh

2

É uma boa forma de fazê-lo quando o membro não faz sentido no contexto. Por exemplo, se você criar uma coleção somente leitura implementada IList<T>delegando a um objeto interno _wrapped, poderá ter algo como:

public T this[int index]
{
  get
  {
     return _wrapped[index];
  }
}
T IList<T>.this[int index]
{
  get
  {
    return this[index];
  }
  set
  {
    throw new NotSupportedException("Collection is read-only.");
  }
}
public int Count
{
  get { return _wrapped.Count; }
}
bool ICollection<T>.IsReadOnly
{
  get
  {
    return true;
  }
}

Aqui temos quatro casos diferentes.

public T this[int index]é definido por nossa classe e não pela interface e, portanto, obviamente não é uma implementação explícita, embora observe que é semelhante à leitura-gravação T this[int index]definida na interface, mas é somente leitura.

T IList<T>.this[int index]é explícito porque uma parte (o getter) corresponde perfeitamente à propriedade acima e a outra parte sempre gera uma exceção. Embora seja vital para alguém acessar uma instância desta classe por meio da interface, não faz sentido alguém usá-la através de uma variável do tipo da classe.

Da mesma forma, porque bool ICollection<T>.IsReadOnlysempre retornará true, é totalmente inútil o código escrito contra o tipo da classe, mas pode ser vital para usá-lo através do tipo da interface e, portanto, nós o implementamos explicitamente.

Por outro lado, public int Countnão é implementado explicitamente porque poderia ser útil para alguém que usa uma instância por meio de seu próprio tipo.

Mas com o seu caso "muito raramente usado", eu me inclinaria muito fortemente para não usar uma implementação explícita.

Nos casos em que eu recomendo usar uma implementação explícita, chamar o método por meio de uma variável do tipo da classe seria um erro (tentando usar o setter indexado) ou inútil (verificar um valor que sempre será o mesmo) para ocultar você está protegendo o usuário de código incorreto ou subótimo. Isso é significativamente diferente do código que você acha que provavelmente será usado raramente . Por isso, eu poderia considerar usar o EditorBrowsableatributo para ocultar o membro do intellisense, embora eu estivesse cansado disso; o cérebro das pessoas já tem seu próprio software para filtrar o que não lhes interessa.


Se você estiver implementando uma interface, implemente a interface. Caso contrário, isso é uma clara violação do Princípio de Substituição de Liskov.
Telastyn #

@ Telastyn a interface é realmente implementada.
Jon Hanna

Mas o contrato não está satisfeito - especificamente, "Isso é algo que representa uma coleção de acesso aleatório e mutável".
Telastyn

1
O @Telastyn cumpre o contrato documentado, principalmente à luz de "Uma coleção que é somente leitura não permite a adição, remoção ou modificação de elementos após a criação da coleção". Consulte msdn.microsoft.com/pt-br/library/0cfatk9t%28v=vs.110%29.aspx
Jon Hanna

@Telastyn: Se o contrato incluísse mutabilidade, por que a interface conteria uma IsReadOnlypropriedade?
Nikie 28/10
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.