Ковариантный тип возвращаемого значения для свойства auto остается нулевым при инициализации в конструкторе

#c# #c#-9.0

Вопрос:

У меня возникла проблема с автоматической инициализацией свойств с ковариантным типом возврата. В конструкторе базового класса нет исключения, но свойство auto остается пустым.

Я нашел обходной путь, переопределив свойство и выполнив приведение в теле get (см. Комментарий ниже).

У вас есть объяснение этому поведению ? И есть ли лучший обходной путь ?

С уважением,

PS : Я совершил ошибку. Конечно, переопределенное свойство должно быть окончательной собственностью, а не базовой собственностью.

 class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
        Console.WriteLine(new FinalClass(new FinalProperty()).MyProperty == null); // true
    }
}

public class BaseClass
{
    public BaseClass(BaseProperty prop)
    {
        MyProperty = prop;
        Console.WriteLine($"ctor A : {MyProperty == null}"); // true
    }

    public virtual BaseProperty MyProperty { get; }
}



public class FinalClass : BaseClass
{
    public FinalClass(BaseProperty prop)
        : base(prop)
    { }

    public override FinalProperty MyProperty { get; }

    //public override FinalProperty MyProperty
    //{
    //    get { return (FinalProperty)base.MyProperty; }
    //}
}

public class BaseProperty
{ }

public sealed class FinalProperty : BaseProperty
{ }
 

Комментарии:

1. Это потому, что вы переопределяете MyProperty вход FinalClass и устанавливаете тот, который в BaseClass . Если вы собираетесь переопределить его, то вам следует установить его в FinalClass конструкторе. Также здесь нет ковариации, потому что вы не используете дженерики.

2. Как это связано с ковариантными типами возвращаемых данных? MyProperty у in FinalClass тот же тип, MyProperty что и у in BaseClass .

3. В частности, проблема заключается в том, что вы переопределяете get so только тогда, когда устанавливаете его в поле BaseClass оно установлено в скрытое поле в этом классе, но переопределенное попадание в поле FinalClasss будет извлекаться из нового скрытого поля, определенного в FinalClass

Ответ №1:

Почему это происходит — для каждого MyProperty переопределенного в дочерних классах с помощью компилятора автоматического свойства будет создано новое резервное поле для этого класса, т. е. следующее:

 public class FinalClass : BaseClass
{
    public FinalClass(FinalProperty prop)
            : base(prop)
    { }

    public override FinalProperty MyProperty { get; }
}
 

Будет преобразован компилятором во что-то вроде:

 public class FinalClass : BaseClass
{
    [CompilerGenerated]
    private readonly FinalProperty <MyProperty>k__BackingField;

    public virtual FinalProperty MyProperty
    {
        [PreserveBaseOverrides]
        [CompilerGenerated]
        get
        {
            return <MyProperty>k__BackingField;
        }
    }

    public FinalClass(FinalProperty prop)
        : base(prop)
    {
    }
}
 

Таким BaseClass(BaseProperty prop) образом, вызываемое base(prop) будет устанавливать резервное поле, сгенерированное для MyProperty in BaseClass , в то время FinalClass.MyProperty как будет использоваться сгенерированное для него.

Другим возможным обходным путем является использование универсальных методов вместо ковариантных возвратов:

 public class BaseClass<T> where T: BaseProperty
{
    public BaseClass(T prop)
    {
        MyProperty = prop;
        Console.WriteLine($"ctor A : {MyProperty == null}"); // true
    }
    public T MyProperty { get; }
}

public class FinalClass : BaseClass<FinalProperty>
{
    public FinalClass(FinalProperty prop)
        : base(prop)
    { }
}
 

Ответ №2:

Хотя вы переопределили MyProperty FinalClass , сделав это:

 public override FinalProperty MyProperty { get; }
 

Тот факт, что это автоматическое свойство, все равно создает дополнительное поле для резервного FinalClass копирования . Давайте уберем синтаксический сахар из автоматических свойств, чтобы увидеть, что происходит:

 public class BaseClass
{
    public BaseClass(BaseProperty prop)
    {
        // notice that when setting a get-only field in the constructor, 
        // you are actually setting its backing field
        backingFieldMyPropertyInBaseClass = prop;
        Console.WriteLine($"ctor A : {MyProperty == null}"); // true
    }

    private BaseProperty backingFieldMyPropertyInBaseClass;
    public virtual BaseProperty MyProperty { 
        get { return backingFieldMyPropertyInBaseClass; }
    }
}
public class FinalClass : BaseClass
{
    public FinalClass(BaseProperty prop)
        : base(prop)
    { }

    private FinalProperty backingFieldMyPropertyInFinalClass;
    public override FinalProperty MyProperty { 
        get { return backingFieldMyPropertyInFinalClass; }
    }
}
 

Обратите внимание, как backingFieldMyPropertyInFinalClass никогда не устанавливается.

Создание MyProperty в FinalClass ответ base.MyProperty исправляет это, потому base.MyProperty что на самом деле возвращает поле, которое вы хотите — backingFieldMyPropertyInBaseClass . Вы также можете заставить его печатать false Main , установив MyProperty в конструкторе значение FinalClass , чтобы оно backingFieldMyPropertyInFinalClass было установлено.

Комментарии:

1. Я не могу добавить защищенный задатчик в MyPpoperty , потому что ковариантный тип возврата запрещает это. Я установил MyProperty в конструкторе FinalClass , но я хотел бы иметь возможность использовать MyProperty и в конструкторе базового класса, потому что он используется другими свойствами, инициализированными в конструкторе базового класса.

2.@R1Tech Ну, в таком случае ваш дизайн проблематичен. BaseClass невозможно безопасно установить переопределенную версию MyProperty , потому что вы переопределили ее более специализированным типом и BaseClass не знаете, что это за тип. Смотрите конец ответа Гуру Строна, чтобы BaseClass узнать, каков тип переопределенного свойства.

3. Но я хотел бы иметь возможность инициализировать базовое свойство в базовом конструкторе и использовать его в этом конструкторе, затем инициализировать конечное свойство в конечном конструкторе и использовать его. И BaseClass он не знает, что это за тип и что это за тип MyProperty .