Why can’t I assign a list of a more specific type than the declared type?

Asked

Viewed 179 times

3

I have this class example:

public class User : APerson
    {
        private string _userName;

        [DataMember]
        public virtual string UserName
        {
            get { return _userName; }
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    throw new FormatException(ErrorMessage.User.USERNAME_REQUIRED);
                }

                if (value.Length > 50)
                {
                    throw new FormatException(ErrorMessage.User.USERNAME_TOO_LONG);
                }

                _userName = value;
            }
        }

        [DataMember]
        public virtual string Password { get; set; }

        [DataMember]
        public virtual bool IsActiveDirectory { get; set; } = false;

        [IgnoreDataMember]
        public virtual IList<Application> Applications { get; set; }

        [IgnoreDataMember]
        public virtual IList<UserAccessKey> UserAccessKeys { get; set; }

        [IgnoreDataMember]
        public virtual IList<UserApplication> UserApplications { get; set; }

        [IgnoreDataMember]
        public virtual AClient Client { get; set; }

        [IgnoreDataMember]
        public virtual IList<UserLog> UserLogs { get; set; }
    }

The Aclient, is this abstract class:

public abstract class AClient
    {
        private string _companyName;
        private string _company;

        [DataMember]
        public virtual int Id { get; set; }

        [DataMember]
        public virtual Guid Hash { get; set; }

        [DataMember]
        public virtual bool IsManager { get; set; }

        [DataMember]
        public virtual string CompanyName
        {
            get { return _companyName; }
            set
            {
                if (!string.IsNullOrEmpty(value) && value.Length > 100)
                {
                    throw new Exception(ErrorMessage.Client.COMPANY_NAME_TOO_LOG);
                }
                _companyName = value;
            }
        }

        [DataMember]
        public virtual string Company
        {
            get { return _company; }
            set
            {
                if (!string.IsNullOrEmpty(value) && value.Length > 150)
                {
                    throw new Exception(ErrorMessage.Client.COMPANY_TOO_LONG);
                }
                _company = value;
            }
        }

        [DataMember]
        public virtual bool IsActive { get; set; }

        [DataMember]
        public virtual string CssFileExtensionName { get; set; }

    }

So far so good. The problem is that if I via LINQ try to locate in the Client attribute of the User class, the Description attribute, which does not exist in the abstract class, I will have a build error.

What to do in these cases? How to explain to LINQ what concrete class it inherits from the abstract class, which has this attribute.

In addition: If I have the following example:

public virtual IList<APerson> Patients { get; set; } = new List<Patient>();

I have a compilation error, even the Patient class inheriting from Aperson.

If I do this below, the second line works. The first no!

public virtual IList<APerson> Patients { get; set; } = new List<Patient>();

public APerson person { get; set; } = new Patient();

I cannot understand these problems. Can someone give me a light? Note: Starting now with ID.

  • Right, but your questioning has no relation to dependency injection, can paste the content of the build error into the question?

  • No??? But in the injection of dependency you prefer interfaces and abstract classes instead of concrete classes, no? My question is how to program this way. Which category does it indicate? And regarding the error, I’m not in the machine now but the error informs that Patient is not an aperson... the craziest and that if I make a simple attribute, not a list, it’s sure... Already set that example

  • 2

2 answers

7


What’s the matter with List<Animal> lista = new List<Girafa>() ?

It doesn’t work because the generic class List<T> allows inserting into the list.

See, I can’t add Camelo to a list of giraffes:

List<Animal> animais = new List<Girafa>();
animais.Add(new Camelo()); // se a lista é de animais,
                           // então deveria ser possível
                           // inserir camelos nela

Using LINQ with a list of the base class

If you have a list of Animal, and wants to treat only the elements that are Girafa, of the two a:

  • Cast all elements for Giraffe:

    animais.Cast<Girafa>().Select(girafa => girafa.PropriedadeDaGirafa);
    
  • Selects from all animals, only those who are Girafa:

    animais.OfType<Girafa>().Select(girafa => girafa.PropriedadeDaGirafa);
    

The choice depends on whether you know beforehand that all the elements on the list are in fact giraffes or whether there may be other different animals on the list.

What’s with the variance talk?

Variance is the way the type varies with the generic parameter (it is easier to understand with examples! =D ). In C# only variance applies to generic interfaces and delegates.

  • Covariance: suppose an interface IS<T>. IS and T are covariant when IS varies along with T. So this is valid:

    Animal a = (Girafa)g;
    IS<Animal> ia = (IS<Girafa>)ig;
    
  • Counter-variance: suppose another interface IE<T>. IE and T are covariants is when IE varies contrary to T. So this is valid:

    Animal a = (Girafa)g;
    I<Girafa> ig = (I<Animal>)ia;
    

One question that remains is what makes a type covariant or counter-variant with respect to its parameter?

Or rather, what characteristic of the type makes the above examples true?

I will explain using the interfaces of the above examples.

Covariance - IS<T>

Occurs when T is used only as output type IS<T>. Therefore, the generic parameter with out:

interface IS<out T>
{
    T LerValor();
}

Let’s test it to see if it’s gonna be a problem:

IS<Animal> ia = (IS<Girafa>)ig;
Animal a = ia.LerValor(); // parece bom... IS<Girafa>.LerValor()
                          // retorna Girafa, que é um Animal.
                          // Beleza!

Counter-variance - IE<T>

Occurs when T is used only as input type IE<T>. Therefore, the generic parameter with in:

interface IE<in T>
{
    void EscreveValor(T valor);
}

Let’s test it to see if it’s gonna be a problem:

IE<Girafa> ig = (IE<Animal>)ia;
ig.EscreveValor( (Girafa)g ); // parece bom... IE<Animal>.EscreveValor()
                              // recebe Animal, então se eu só puder passar
                              // Girafa's tá de boa, pois Girafa é Animal.
                              // Beleza!

Composition of various levels of variance

It’s easier to understand using delegates in this case.

I’ll define them like this:

delegate T DS<out T>();
delegate void DE<in T>(T valor);

Let’s go to some affirmations and some codes to demonstrate:

  • The exit is an exit

        DS<DS<Girafa>> ssg = () => () => new Girafa();
        DS<DS<Animal>> ssa = ssg;
    
        // vou receber uma girafa (como sendo um animal)
        Animal a = ssa()();
    
  • The exit entrance is an entrance

        DS<DE<Animal>> sea = () => a => Console.WriteLine(a);
        DS<DE<Girafa>> seg = sea;
    
        // vou passar uma girafa (mas o delegate sabe usar qualquer animal)
        var g = new Girafa();
        seg()(g);
    
  • The exit from the entrance is an entry

        DE<DS<Animal>> esa = sa => Console.WriteLine(sa());
        DE<DS<Girafa>> esg = esa;
    
        // vou passar uma girafa (mas o delegate sabe usar qualquer animal)
        var g = new Girafa();
        esg(() => g);
    
  • The entrance to the entrance is an exit

        DE<DE<Girafa>> eeg = eg => eg(new Girafa());
        DE<DE<Animal>> eea = eeg;
    
        // vou receber uma girafa (através do delegate)
        Animal a;
        eea(a2 => a = a2);
    

I tried to make an image to explain, I don’t know if you’re confused, if you’re telling me I change or take it.

Composição de níveis de variância

Titanic compositions

Let’s go to some more, shall we say, complex examples:

  • The entrance of the exit from the entrance to the entrance... what would it be? I already answer: it is entrance.

        DE<DE<DS<DE<Animal>>>> eeseg = null;
        DE<DE<DS<DE<Girafa>>>> eesea = eeseg;
    
  • And the entrance of exit 5 from the exit of the entrance to the entrance... what would it be? Straight to the point: is exit.

        DE<DE<DS<DE<DS<DS<DS<DS<DS<DE<Girafa>>>>>>>>>> eesessssseg = null;
        DE<DE<DS<DE<DS<DS<DS<DS<DS<DE<Animal>>>>>>>>>> eesesssssea = eesessssseg;
    

You can answer these questions very quickly. Just count the entries.

  • Even number of entries is output

  • Odd number of entries is input

But what about the exits?

A: Exits do not affect anything

  • Output 10000: is output because it has even number of inputs (0 is even)

  • Output 100 from output 101: is input because it has odd number of inputs (only 1 input)

  • In the image, are you writing in pure English or do you have Portuguese? More specifically: and, what language is the of of the image?

  • 1

    Haha... the "do" is really Portuguese, yeah, it got weird. The bad thing is that OUT and IN was going to be super long. I don’t know what to do... maybe replace "do" with a " ", or write in English anyway.

1

@ramaral indicated in the comments the question that has the answer.

This problem happens because the guy Patient is not cowardly in relation to APerson. What this means is that Patiente is not a more generic type than APerson.

The problem is that your list of APerson cannot guarantee you that there will be no running problems, when you try to add an object of a different type from Patient but inherited from APerson.

When you try to assign one Patient to a variable of the type APerson is all right. The guy Patient is contravariant in relation to APerson. That means he’s a sub-type of APerson. You could also assign any other sub-type that there would be no problems.

This is simpler to understand by the fact that all types are contravarients in relation to object.

In short, the way to solve your problem is to change the type of the patient list to the generic type Patient.

public virtual IList<Patient> Patients { get; set; } = new List<Patient>();

You could also keep your generic list, but in this case Voce cannot guarantee that you will only find objects of the type Patient in it.

public virtual IList<APerson> Patients { get; set; } = new List<APerson>();

Browser other questions tagged

You are not signed in. Login or sign up in order to post.