Existe uma maneira de obter a seguinte declaração de função?
public bool Foo<T>() where T : interface;
ie onde T é um tipo de interface (semelhante a where T : class
, e struct
).
Atualmente, eu me conformei com:
public bool Foo<T>() where T : IBase;
Onde o IBase é definido como uma interface vazia que é herdada por todas as minhas interfaces personalizadas ... Não é o ideal, mas deve funcionar ... Por que você não pode definir que um tipo genérico deve ser uma interface?
Pelo que vale, eu quero isso porque Foo
está refletindo onde precisa de um tipo de interface ... Eu poderia passar como um parâmetro normal e fazer a verificação necessária na própria função, mas isso parecia muito mais seguro (e eu suponha um pouco mais de desempenho, uma vez que todas as verificações são feitas em tempo de compilação).
c#
generics
interface
constraints
Matthew Scharley
fonte
fonte
IBase
- usadas dessa maneira - são chamadas de interfaces de marcador . Eles permitem comportamentos especiais para tipos 'marcados'.Respostas:
O mais próximo que você pode fazer (exceto a abordagem da interface base) é "
where T : class
", significando o tipo de referência. Não há sintaxe para significar "qualquer interface".Isso ("
where T : class
") é usado, por exemplo, no WCF para limitar os clientes a contratos de serviço (interfaces).fonte
interface
restriçãoT
deve permitir comparações de referência entreT
e qualquer outro tipo de referência, uma vez que são permitidas comparações de referência entre qualquer interface e quase qualquer outro tipo de referência, e permitir comparações mesmo nesse caso não apresentaria problemas.Eu sei que isso é um pouco tarde, mas para aqueles que estão interessados, você pode usar uma verificação de tempo de execução.
fonte
Foo(Type type)
.if (new T() is IMyInterface) { }
para verificar se uma interface é implementada pela classe T. Pode não ser o mais eficiente, mas funciona.Não, na verdade, se você está pensando
class
estruct
quer dizerclass
es estruct
s, está errado.class
significa qualquer tipo de referência (por exemplo, também inclui interfaces) estruct
significa qualquer tipo de valor (por exemplostruct
,enum
).fonte
where T : struct
restrição.class
, mas declarar um local de armazenamento de um tipo de interface realmente declara que o local de armazenamento é uma referência de classe que implementa esse tipo.where T : struct
corresponde aNotNullableValueTypeConstraint
, portanto significa que deve ser um tipo de valor diferente deNullable<>
. (EntãoNullable<>
é um tipo struct que não satisfaz awhere T : struct
restrição.)Para acompanhar a resposta de Robert, isso é ainda mais tarde, mas você pode usar uma classe auxiliar estática para fazer a verificação do tempo de execução apenas uma vez por tipo:
Também observo que sua solução "deve funcionar" não funciona. Considerar:
Agora não há nada que o impeça de chamar Foo assim:
A
Actual
classe, afinal, satisfaz aIBase
restrição.fonte
static
construtor não pode serpublic
, portanto, isso deve gerar um erro em tempo de compilação. Além disso, suastatic
classe contém um método de instância, que também é um erro em tempo de compilação.Já faz algum tempo que estou pensando em restrições de tempo de compilação, portanto esta é uma oportunidade perfeita para lançar o conceito.
A idéia básica é que, se você não puder verificar o tempo de compilação, faça-o o mais cedo possível, que é basicamente o momento em que o aplicativo é iniciado. Se todas as verificações estiverem corretas, o aplicativo será executado; se uma verificação falhar, o aplicativo falhará instantaneamente.
Comportamento
O melhor resultado possível é que nosso programa não seja compilado se as restrições não forem atendidas. Infelizmente, isso não é possível na implementação atual do C #.
A melhor coisa a seguir é que o programa falha no momento em que é iniciado.
A última opção é que o programa falhe no momento em que o código for atingido. Esse é o comportamento padrão do .NET. Para mim, isso é completamente inaceitável.
Pré requisitos
Precisamos ter um mecanismo de restrição, portanto, pela falta de algo melhor ... vamos usar um atributo. O atributo estará presente em cima de uma restrição genérica para verificar se ele corresponde às nossas condições. Caso contrário, damos um erro feio.
Isso nos permite fazer coisas assim em nosso código:
(Eu mantive o
where T:class
aqui, porque eu sempre prefiro verificações em tempo de compilação a verificações em tempo de execução)Portanto, isso nos deixa com apenas 1 problema, que é verificar se todos os tipos que usamos correspondem à restrição. Quão difícil isso pode ser?
Vamos terminar
Os tipos genéricos estão sempre em uma classe (/ struct / interface) ou em um método.
O acionamento de uma restrição requer que você execute um dos seguintes procedimentos:
Neste ponto, gostaria de afirmar que você deve sempre evitar fazer (4) em qualquer programa IMO. Independentemente disso, essas verificações não serão compatíveis, pois significariam efetivamente resolver o problema da interrupção.
Caso 1: usando um tipo
Exemplo:
Exemplo 2:
Basicamente, isso envolve a varredura de todos os tipos, herança, membros, parâmetros, etc, etc, etc. Se um tipo é um tipo genérico e tem uma restrição, verificamos a restrição; se for uma matriz, verificamos o tipo de elemento.
Neste ponto, devo acrescentar que isso quebrará o fato de que, por padrão, o .NET carrega os tipos 'preguiçoso'. Ao verificar todos os tipos, forçamos o tempo de execução do .NET a carregar todos eles. Para a maioria dos programas, isso não deve ser um problema; ainda assim, se você usar inicializadores estáticos no seu código, poderá encontrar problemas com essa abordagem ... Dito isso, eu não aconselharia ninguém a fazer isso de qualquer maneira (exceto para coisas como esta :-), por isso não deve dar você muitos problemas.
Caso 2: usando um tipo em um método
Exemplo:
Para verificar isso, temos apenas 1 opção: descompilar a classe, verificar todos os tokens de membros que são usados e se um deles for do tipo genérico - verifique os argumentos.
Caso 3: Reflexão, construção genérica de tempo de execução
Exemplo:
Suponho que seja teoricamente possível verificar isso com truques semelhantes ao caso (2), mas a implementação é muito mais difícil (você precisa verificar se
MakeGenericType
é chamado em algum caminho de código). Não vou entrar em detalhes aqui ...Caso 4: Reflexão, RTTI em tempo de execução
Exemplo:
Este é o pior cenário possível e, como expliquei antes, geralmente é uma péssima ideia IMHO. De qualquer maneira, não há uma maneira prática de descobrir isso usando cheques.
Testando o lote
Criar um programa que teste os casos (1) e (2) resultará em algo como isto:
Usando o código
Bem, essa é a parte mais fácil :-)
fonte
Você não pode fazer isso em nenhuma versão lançada do C #, nem no próximo C # 4.0. Também não é uma limitação de C # - não há restrição de "interface" no próprio CLR.
fonte
Se possível, eu fui com uma solução como esta. Funciona apenas se você deseja que várias interfaces específicas (por exemplo, aquelas às quais você tem acesso de origem) sejam passadas como um parâmetro genérico, e não qualquer.
IInterface
.IInterface
Na fonte, fica assim:
Qualquer interface que você deseja que seja transmitida como parâmetro genérico:
IInterface:
A classe na qual você deseja colocar a restrição de tipo:
fonte
T
não está restrito a interfaces, está restrito a qualquer coisa que implementeIInterface
- o que qualquer tipo pode fazer se quiser, por exemplo,struct Foo : IInterface
uma vez que vocêIInterface
é provavelmente público (caso contrário, tudo o que aceita deve ser interno).O que você decidiu é o melhor que pode fazer:
fonte
Tentei fazer algo semelhante e usei uma solução alternativa: pensei no operador implícito e explícito na estrutura: a idéia é envolver o Type em uma estrutura que possa ser convertida em Type implicitamente.
Aqui está uma estrutura:
estrutura pública InterfaceType {private Type _type;
}
uso básico:
Você deve imaginar seu próprio mecanismo em torno disso, mas um exemplo pode ser um método usado como um InterfaceType no parâmetro, em vez de um tipo
Um método para substituir que deve retornar os tipos de interface:
Talvez haja coisas a ver com genéricos também, mas eu não tentei
Espero que isso possa ajudar ou dê idéias :-)
fonte
Solução A: Essa combinação de restrições deve garantir que
TInterface
é uma interface:Requer uma estrutura única
TStruct
como testemunha para provar queTInterface
é uma estrutura.Você pode usar uma única estrutura como testemunha para todos os seus tipos não genéricos:
Solução B: Se você não deseja criar estruturas como testemunhas, pode criar uma interface
e use uma restrição:
Implementação para interfaces:
Isso resolve alguns dos problemas, mas requer confiança que ninguém implementa
ISInterface<T>
para tipos que não são de interface, mas isso é bastante difícil de fazer acidentalmente.fonte
Use uma classe abstrata em seu lugar. Então, você teria algo como:
fonte