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

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 were to see LINQ try to find in the Client attribute of the User class, the Description attribute, which does not exist in the abstract class, I will have a compilation error.

What to do in these cases? How to explain to LINQ which concrete class 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 compile error, even the Patient class inheriting from APerson.

If I do this below, the second line works. The first one is not!

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

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

I can not understand these problems. Can anyone give me a light? Note: Starting now with ID

    
asked by anonymous 21.09.2017 / 19:29

2 answers

7

What is the problem with List<Animal> lista = new List<Girafa>() ?

It does not work because the generic class List<T> allows to insert in the list.

See, I can not add Camel in 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 base class

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

  • Cast all elements for Giraffe:

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

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

The choice depends on you knowing beforehand that all elements of the list are indeed Giraffes or whether there may be other different animals in the list.

What is the Variance Chat?

Variance is the way the type varies with the generic parameter (it is easier to understand with examples! = D). In C # variance only 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;
    
  • Anti-variance: suppose another interface IE<T> . IE and T are covariates is IE varies contrary to T . So this is valid:

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

A leftover question is what makes a type covariant or counter-variant with respect to its parameter?

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

I'll explain using the interfaces of the examples above.

Covariance - IS<T>

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

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

Let's test to see if it's going to 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 only used as input of type IE<T> . Therefore, you enter the generic parameter with in :

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

Let's test to see if it's going to 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 several 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);

Here are some statements and some code to demonstrate:

  • Output output is output

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

        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 output of the input is an input

        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);
    
  • Input input is output

        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 do not know if it is confusing, if you are told that I change or shoot it.

Titanic compositions

Let's take a few more, let's say, complex examples:

  • The input from the input input output ... what would it be? I already answer: it's input.

        DE<DE<DS<DE<Animal>>>> eeseg = null;
        DE<DE<DS<DE<Girafa>>>> eesea = eeseg;
    
  • And the input of output ^ 5 from input input of output input ... what would it be? Straight to the point: it's output.

        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;
    

These questions can be answered very quickly. Just count the entries.

  • Number of inputs is output

  • Odd number of entries is input

But what about the exits?

R: Exits do not affect at all

  • The output of the ^ 10000 output is output because it has even number of inputs (0 is even)

  • The output ^ 100 of the output input ^ 101: is input because it has odd number of inputs (only 1 input)

29.09.2017 / 00:36
1

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

This problem occurs because the type Patient is not covariant relative to APerson . What this means is that Patiente is not a generic type that APerson .

The problem is that your list of APerson can not guarantee you that there will be no problems running when you try to add an object of a type other than Patient but inherit from APerson .

Already when you try to assign a Patient to a variable of type APerson everything is correct. The type Patient is contravariant relative to APerson . This means that it is subtype of APerson . You could also assign any other sub-type that would be no problem.

This is simpler to understand by the fact that all types are contravariants relative to object .

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

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

You could also maintain your generic list, but in this case you can not assure that you will only find objects of type Patient in it.

public virtual IList<APerson> Patients { get; set; } = new List<APerson>();
    
26.09.2017 / 11:12