1
votes

Impossible de compiler lors de l'utilisation de la liste des génériques en C #

J'ai deux classes différentes. Et ces deux classes différentes ont également plusieurs propriétés. Considérez les deux classes d'exemple suivantes,

Severity    Code    Description Project File    Line    Suppression State
Error   CS1061  'T' does not contain a definition for 'Name' and no accessible extension method 'Name' accepting a first argument of type 'T' could be found (are you missing a using directive or an assembly reference?)

Remarque: Par exemple, j'ai ajouté la classe comme ci-dessus. Mais, en réalité, les deux classes devraient avoir des valeurs différentes avec le même nom.

Ces classes devraient être un membre de la liste comme ci-dessous,

private string GetValue<T>(List<T> listValue)
{
    string toRet = string.Empty;
    if (listValue == null)
        return toRet;

    foreach (T iter in listValue)
    {
        if (!string.IsNullOrWhiteSpace(iter.Key)) {
            toRet = val.Name;
            break;
        }
    }

    return toRet;
}

Donc, pour traiter ces listes, j'ai besoin de deux fonctions différentes, quelque chose comme ci-dessous,

private string GetStrValue(List<Class1> values)
{
    string toRet = string.Empty;
    if (values == null)
        return toRet;

    foreach (Class1 val in values)
    {
        if (!string.IsNullOrWhiteSpace(val.Key)) {
            toRet = val.value;
            break;
        }
    }

    return toRet;
}

Et, la fonction similaire pour traiter la classe Int également. Donc, j'ai prévu d'utiliser le générique. J'ai écrit le code comme ci-dessous,

List<Class1> list1 = new List<Class1>();
List<Class2> list2 = new List<Class2>();

Mais, le code ne se compile pas. Je suis confronté à l'erreur ci-dessous.

public Class1{
    public string Key;
    public string value;
}

public Class2{
    public string Key;
    public string value;
}

Nous serions très reconnaissants à toute personne d'aider à ce sujet.

Merci,

p>


3 commentaires

Class1 et Class2 ont-ils des propriétés supplémentaires qu'ils n'ont pas en commun?


Je vous donne l ' interface ... interface publique ISomething {string Key; valeur de chaîne; } ajoutez-le à vos classes, vous pouvez alors contraindre votre méthode générique par elle. où T: ISomething


Oui Sean. Ils ont aussi des propriétés supplémentaires ...


4 Réponses :


4
votes

Vous avez dit "Ce code fonctionne pour chaque type T" et pourtant vous vous attendez à ce que votre Type T ait une propriété appelée Name, que de nombreux types n'ont pas. Les génériques ne fonctionnent pas de cette façon.

Si vous voulez faire quelque chose avec votre instance de type T qui l'oblige à avoir certaines propriétés, vous devez dire au compilateur que T est contraint aux types qui ont ces propriétés.

Dans votre cas, vous devrez écrire une interface commune à vos deux classes et ajouter cette interface à votre définition générique en T.

Ceci est bien expliqué (y compris un bon exemple avec du code) dans la documentation Microsoft ici


0 commentaires

3
votes

Vous avez 2 options interface ou classe père . Bot 2 manières nécessitent où T: interfaceName ou où T: FatherClassName syntaxe

Par exemple avec interface:

private string GetValue<T>(List<T> listValue) where T : IClass
{
    string toRet = string.Empty;
    if (listValue == null)
        return toRet;

    foreach (T val in listValue)
    {
        if (!string.IsNullOrWhiteSpace(val.Key))
        {
            toRet = val.Name;
            break;
        }
    }

    return toRet;
}

Alors votre classe générique serait

public interface IClass
{
    string Key { get; set; }
    string Name { get; set; }
    string value { get; set; }
}
public class Class1 : IClass
{
    public string Key { get; set; }
    public string value { get; set; }
    public string Name { get; set; }
}

public class Class2 : IClass
{
    public string Key { get; set; }
    public string value { get; set; }
    public string Name { get; set; }
}


0 commentaires

2
votes

Vous pouvez le rendre complètement générique en utilisant Reflection (reportez-vous à Reflection | PropertyInfo ). De cette façon, vous seriez en mesure de gérer toutes les classes qui vous viennent. Veuillez vous référer à l'exemple de code ci-dessous:

private string GetValue<T>(List<T> listValue)
{
    string toRet = string.Empty;
    if (listValue == null)
        return toRet;
    PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

    //Since you have mentioned all classes will have "Key" and "Value" and you need to use that only
    //To make it completely generic you can maybe get this as input to this function

    PropertyInfo keyProperty = properties.FirstOrDefault(x => x.Name.Equals("Key", StringComparison.OrdinalIgnoreCase));
    PropertyInfo valueProperty = properties.FirstOrDefault(x => x.Name.Equals("Value", StringComparison.OrdinalIgnoreCase));

    if (keyProperty == null || valueProperty == null)
        return toRet;

    foreach (T iter in listValue)
    {
        var keyData = keyProperty.GetValue(iter, null);
        if (keyData != null && !string.IsNullOrWhiteSpace(keyData.ToString()))
        {
            toRet = valueProperty.GetValue(iter, null).ToString();
            break;
        }
    }

    return toRet;
}


1 commentaires

Notez que bien que cela fonctionne, cela a des implications sur les performances. Si les classes partagent toutes des propriétés communes (clé et valeur), il vaut mieux définir une interface et contraindre le paramètre générique comme le font les autres réponses



2
votes

Comme nvoigt mentionné, dans cette situation, nous devons utiliser le concept " Interface ".

Définissez vos classes et votre interface comme ci-dessous:

        List<IKeyValue> keyValues = new List<IKeyValue>
        {new A{Key="a",Value="b"}, new B{Key="x",Value="y"}};

        List<A> listA = new List<A> 
        { new A { Key = "h", Value = "b" }, new A { Key = "u", Value = "m" } };

        List<B> listB = new List<B> 
        { new B { Key = "h", Value = "b" }, new B { Key = "u", Value = "m" } };

        string resultListInterface = GetValue(keyValues); //valid
        string resultListA = GetValue(listA);             //valid
        string resultListB = GetValue(listB);             //valid

Et votre méthode qui va utiliser "Key" et "Value" devrait être comme ceci:

    private string GetValue<T>(List<T> listValue) where T: IKeyValue
    {
        string toRet = string.Empty;
        if (listValue == null)
            return toRet;

        foreach (T iter in listValue)
        {
            if (!string.IsNullOrWhiteSpace(iter.Key))
            {
                toRet = iter.Value;
                break;
            }
        }

        return toRet;
    }

'où T: IKeyValue' dans la définition de méthode signifie que le type T est ' IKeyValue ', ce qui fait que je peux accéder au ' Key 'et' Value 'en contexte (uniquement ceux qui sont dans l'interface IKeyValue )

Voici comment vous pouvez l'utiliser :

public interface IKeyValue
{
    public string Key { get; set; }
    public string Value { get; set; }
}

public class A : IKeyValue
{
    public string Key { get; set; }
    public string Value { get; set; }
    //other properties... 
}

public class B : IKeyValue
{
    public string Key { get; set; }
    public string Value { get; set; }
    //other properties...
}

Pour convention de dénomination a> Je change le nom de la propriété de " valeur " à " Valeur "


0 commentaires