Compiler falhar a conversão de um tipo genérico restrita
-
06-07-2019 - |
Pergunta
Eu tenho uma classe que tem um tipo genérico "G"
No meu modelo de classe eu tenho
public class DetailElement : ElementDefinition
Vamos dizer que eu tenho um método como este
public void DoSomething<G>(G generic)
where G : ElementDefinition
{
if (generic is DetailElement)
{
((DetailElement)generic).DescEN = "Hello people"; //line 1
//////
ElementDefinition element = generic;
((DetailElement)element).DescEN = "Hello again"; //line 3
//////
(generic as DetailElement).DescEN = "Howdy"; //line 5
}
else
{
//do other stuff
}
}
Compiler relata um erro na linha 1:
Cannot convert type 'G' to 'DetailElement'
Mas a linha 3 funciona bem. Eu posso solucionar esse problema fazendo o código escrito na linha 5.
O que eu gostaria de saber é por que o compilador informa o erro na linha 1 e não a um na linha 3, dado que, tanto quanto eu sei, eles são idênticos.
edit: estou com medo de que eu poderia estar faltando alguma peça importante da lógica de quadro
edit2: Embora soluções para o erro do compilador são importantes, a minha pergunta é sobre porque o compilador relata um erro na linha 1 e não na linha 3
.Solução
Se G
foi constrangido a ser um DetailElement
(where G : DetailElement
), então você pode ir em frente e G
elenco para ElementDefinition, ou seja, "(ElementDefinition) generic
". Mas porque G
poderia ser outra subclasse de ElementDefinition
diferente DetailElement
em tempo de execução que não vai permitir que ele em tempo de compilação em que o tipo é desconhecido e não verificável.
Na linha 3 do tipo que você fundido a partir de é conhecido por ser um ElementDefinition
então tudo que você está fazendo é um up-cast . O compilador não sei se ele vai ser um elenco succcesful em tempo de execução, mas ele vai confiar em você lá. O compilador não está tão confiante para os genéricos.
O as
operador na linha 5 também pode retornar nulo eo compilador não verifica estaticamente o tipo para ver se é seguro nesse caso. Você pode usar as
com qualquer tipo, e não apenas aqueles que são compatíveis com ElementDefinition
.
De posso fundido de e para o tipo genérico parâmetros no MSDN:?
O compilador só vai deixar você lançar implicitamente parâmetros de tipo genérico de objeto, ou para tipos especificados-restrição.
Tal conversão implícita é do tipo caminho seguro, pois qualquer incompatibilidade é descoberto em tempo de compilação.
O compilador vai deixar você converter explicitamente parâmetros de tipo genérico para qualquer interface, mas não para uma classe:
interface ISomeInterface {...} class SomeClass {...} class MyClass<T> { void SomeMethod(T t) { ISomeInterface obj1 = (ISomeInterface)t;//Compiles SomeClass obj2 = (SomeClass)t; //Does not compile } }
No entanto, você pode forçar um elenco de um parâmetro de tipo genérico para qualquer outro tipo usando uma variável objeto temporário
void SomeMethod<T>(T t) { object temp = t; MyOtherClass obj = (MyOtherClass)temp; }
É desnecessário dizer que tal conversão explícita é perigoso porque pode lançar uma exceção em tempo de execução se o tipo de concreto usado em vez do parâmetro de tipo genérico não derivam do tipo que você explicitamente convertido para.
Em vez de arriscar uma exceção casting, uma abordagem melhor é usar os operadores
is
ouas
. O operadoris
retorna true se o parâmetro de tipo genérico é do tipo consultado eas
irá realizar um elenco se os tipos são compatíveis, e retornará null contrário.public void SomeMethod(T t) { if(t is int) {...} string str = t as string; if(str != null) {...} }
Outras dicas
Geralmente, upcasting é um cheiro de código. Você pode evitá-lo por sobrecarga de método. Tente isto:
public void DoSomething(DetailElement detailElement)
{
// do DetailElement specific stuff
}
public void DoSomething<G>(G elementDefinition)
where G : ElementDefinition
{
// do generic ElementDefinition stuff
}
Você pode então tirar proveito de sobrecarga de método usando este código:
DetailElement foo = new DetailElement();
DoSomething(foo); // calls the non-generic method
DoSomething((ElementDefinition) foo); // calls the generic method
não deve sua cláusula where ser "onde G: DetailElement"?
No código que você escreveu, um DetailElement é um ElementDefinition, mas um ElementDefinition não é necessariamente uma DetailElement. Assim, a conversão implícita é ilegal.
Existem outros tipos de ElementDefinition que você pode passar para este método? Se assim for, eles vão lançar uma exceção quando você tenta lançá-los em instâncias DetailElement.
EDIT:
Ok, então agora que você mudou sua listagem de código, eu posso ver que você está verificando o tipo para se certificar de que realmente é um DetailElement antes de entrar nesse bloco de código. Infelizmente, o fato da questão é que você pode não implicitamente baixos, mesmo se você já verificou os tipos si mesmo. Eu acho que você realmente deveria usar o "como" palavra-chave no início do seu bloco:
DetailElement detail = generic as DetailElement;
if (detail == null) {
// process other types of ElementDefinition
} else {
// process DetailElement objects
}
Melhor ainda, por que não usar polimorfismo para permitir que cada tipo de ElementDefinition para definir seu próprio método DoSomething, e deixar que o CLR cuidar do tipo de verificação e invocação de método para você?
Isto levará a um pouco mais de código se você tem um monte de ElementDefinitions você está preocupado com, mas é provavelmente o mais liso você terá que não envolve é, então, como um disparate.
public void DoSomething<G>(G generic)
where G : ElementDefinition
{
DetailElement detail = generic as DetailElement;
if (detail != null)
{
detail.DescEN = "Hello people";
}
else
{
//do other stuff
}
}
Outra possível solução que eu usei quando eu precisava de tais informações, em loo de uma variável objeto temporário.
DetailElement detail = (DetailElement)(object)generic;
Ele funciona, mas o que forma é provavelmente o melhor.