Como faço para tornar o tipo de retorno de um método genérico?

166

Existe uma maneira de tornar esse método genérico para que eu possa retornar uma string, bool, int ou double? No momento, ele está retornando uma string, mas se puder encontrar "true" ou "false" como o valor da configuração, eu gostaria de retornar um bool, por exemplo.

    public static string ConfigSetting(string settingName)
    {  
         return ConfigurationManager.AppSettings[settingName];
    }
MacGyver
fonte
Existe uma maneira de você saber qual é o tipo de cada configuração?
Thecoshman
2
Eu acho que a pergunta que você realmente deseja fazer é "Como faço para que minha configuração de aplicativo seja fortemente digitada?" Já faz muito tempo desde que eu trabalhei com isso para escrever uma resposta adequada.
21712 Simon
Sim, idealmente, não quero ter que passar o tipo para o método. Eu só vou ter os 4 tipos que eu mencionei. Portanto, se "true" / "false" estiver definido, quero que essa função retorne um booleano (sem a necessidade de passar para o método), provavelmente posso combinar int e double em apenas double, e todo o resto deve ser uma string. O que já foi respondido funcionará bem, mas preciso passar o tipo toda vez, o que provavelmente é bom.
21712 MacGyver
3
Seu comentário parece que você está pedindo um método que retorne um bool fortemente digitado (ou string, ou int ou o que você tem) em tempo de execução, com base nos dados reais recuperados para a chave do nome da configuração. C # não fará isso por você; não há como você saber o tipo desse valor em tempo de compilação. Em outras palavras, é digitação dinâmica, não estática. O C # pode fazer isso por você se você usar a dynamicpalavra - chave. Existe um custo de desempenho para isso, mas para ler um arquivo de configuração, o custo de desempenho é quase certamente insignificante.
Phoog 21/03/12

Respostas:

354

Você precisa torná-lo um método genérico, como este:

public static T ConfigSetting<T>(string settingName)
{  
    return /* code to convert the setting to T... */
}

Mas o chamador terá que especificar o tipo que espera. Você poderia usar potencialmente Convert.ChangeType, assumindo que todos os tipos relevantes sejam suportados:

public static T ConfigSetting<T>(string settingName)
{  
    object value = ConfigurationManager.AppSettings[settingName];
    return (T) Convert.ChangeType(value, typeof(T));
}

Não estou totalmente convencido de que tudo isso seja uma boa ideia, veja bem ...

Jon Skeet
fonte
21
/ * Código para converter a configuração para T ... * / e aqui segue a novela inteira :)
Adrian Iftode
1
Isso não exigiria que você soubesse o tipo de configuração que deseja obter, o que pode não ser possível.
Thecoshman
2
@ thecoshman: Sim, mas se você não o fez, o que você faz com o valor retornado?
George Duckett
5
Embora esta resposta é, naturalmente, correta, e como você notar satisfaz o pedido do OP, é provavelmente vale a pena mencionar que a antiga abordagem de métodos distintos ( ConfigSettingString, ConfigSettingBool, etc.) tem a vantagem de corpos de método que será menor, mais claro, e melhor focados .
Phoog 21/03
4
Se isso não for recomendado, qual é o objetivo dos tipos de retorno genéricos?
precisa saber é o seguinte
29

Você poderia usar Convert.ChangeType():

public static T ConfigSetting<T>(string settingName)
{
    return (T)Convert.ChangeType(ConfigurationManager.AppSettings[settingName], typeof(T));
}
Vidro quebrado
fonte
13

Existem várias maneiras de fazer isso (listadas por prioridade, específicas ao problema do OP)

  1. Opção 1: Abordagem direta - Crie várias funções para cada tipo que você espera, em vez de ter uma função genérica.

    public static bool ConfigSettingInt(string settingName)
    {  
         return Convert.ToBoolean(ConfigurationManager.AppSettings[settingName]);
    }
  2. Opção 2: quando você não deseja usar métodos sofisticados de conversão - converta o valor em objeto e, em seguida, em tipo genérico.

    public static T ConfigSetting<T>(string settingName)
    {  
         return (T)(object)ConfigurationManager.AppSettings[settingName];
    }

    Nota - Isso gerará um erro se o elenco não for válido (seu caso). Eu não recomendaria fazer isso se você não tiver certeza sobre o tipo de conversão, em vez disso, vá para a opção 3.

  3. Opção 3: Genérico com segurança de tipo - Crie uma função genérica para lidar com a conversão de tipo.

    public static T ConvertValue<T,U>(U value) where U : IConvertible
    {
        return (T)Convert.ChangeType(value, typeof(T));
    } 

    Nota - T é o tipo esperado, observe a restrição where aqui (o tipo de U deve ser IConvertible para nos salvar dos erros)

RollerCosta
fonte
4
Por que tornar a terceira opção genérica U? Não faz sentido fazê-lo, e isso torna o método mais difícil de chamar. Apenas aceite IConvertible. Não acho que valha a pena incluir a segunda opção para essa pergunta, pois ela não responde à pergunta que está sendo feita. Você provavelmente deve também mudar o nome do método na primeira opção ...
Jon Skeet
7

Você precisa converter o tipo do seu valor de retorno do método para o tipo genérico que você passa para o método durante a chamada.

    public static T values<T>()
    {
        Random random = new Random();
        int number = random.Next(1, 4);
        return (T)Convert.ChangeType(number, typeof(T));
    }

Você precisa passar um tipo que pode ser convertido para o valor retornado por esse método.

Se você deseja retornar um valor que não é passível de digitação para o tipo genérico que você passa, talvez seja necessário alterar o código ou certifique-se de passar um tipo que pode ser convertido para o valor de retorno do método Portanto, essa abordagem não é recomendada.

Vinay Chanumolu
fonte
Local - para mim a última linha return (T)Convert.ChangeType(number, typeof(T));era exatamente o que eu estava faltando - aplausos
Greg Trevellick
1

Crie uma função e passe o parâmetro put como do tipo genérico.

 public static T some_function<T>(T out_put_object /*declare as Output object*/)
    {
        return out_put_object;
    }
Prabhakar
fonte
Isso é realmente bastante inteligente para alguns casos de uso. Como extrair dados de um banco de dados. Você sabe que receberá uma lista de dados do tipo T. O método de carregamento simplesmente não sabe que tipo de T você deseja agora. Portanto, basta passar uma nova lista <WantedObject> para isso e o método pode fazer seu trabalho e preencher a lista antes de devolvê-la. Agradável!
Marco Heumann
0

Por favor, tente o código abaixo:

public T? GetParsedOrDefaultValue<T>(string valueToParse) where T : struct, IComparable
{
 if(string.EmptyOrNull(valueToParse))return null;
  try
  {
     // return parsed value
     return (T) Convert.ChangeType(valueToParse, typeof(T));
  }
  catch(Exception)
  {
   //default as null value
   return null;
  }
 return null;
}
Sid
fonte
-1
 private static T[] prepareArray<T>(T[] arrayToCopy, T value)
    {
        Array.Copy(arrayToCopy, 1, arrayToCopy, 0, arrayToCopy.Length - 1);
        arrayToCopy[arrayToCopy.Length - 1] = value;
        return (T[])arrayToCopy;
    }

Eu estava realizando isso em todo o meu código e queria uma maneira de colocá-lo em um método. Eu queria compartilhar isso aqui porque não precisava usar o Convert.ChangeType para o meu valor de retorno. Essa pode não ser uma prática recomendada, mas funcionou para mim. Esse método utiliza uma matriz do tipo genérico e um valor a ser adicionado ao final da matriz. A matriz é copiada com o primeiro valor retirado e o valor obtido no método é adicionado ao final da matriz. A última coisa é que eu retorno a matriz genérica.

Adam Howard
fonte