문제

나에서 경고---끝---이력서에 대한 호출하여 가상의 구성원에서 객체 생성자입니다.

왜 이렇게 무언가가 될지 할까?

도움이 되었습니까?

해결책

C#에 작성된 객체가 구성되면, 이니셜 라이저가 가장 파생 된 클래스에서 기본 클래스로 순서대로 실행 한 다음 생성자가 기본 클래스에서 가장 파생 된 클래스로 순서대로 실행됩니다 ()이것이 이유에 대한 자세한 내용은 Eric Lippert의 블로그를 참조하십시오.).

또한 .NET 객체에서는 제작 된대로 유형을 변경하지 않지만 가장 파생 된 유형으로 시작하며 메소드 테이블은 가장 파생 된 유형입니다. 이는 가상 메소드 호출이 항상 가장 파생 된 유형에서 실행됨을 의미합니다.

이 두 가지 사실을 결합하면 생성자에서 가상 메소드를 호출하고 상속 계층에서 가장 파생 된 유형이 아니며 생성자가되지 않은 클래스에서 호출되는 문제가 있습니다. 따라서 실행하므로 해당 방법을 호출하기에 적합한 상태가 아닐 수도 있습니다.

이 문제는 물론 상속 계층에서 가장 파생 된 유형인지 확인하기 위해 클래스를 밀봉 된 것으로 표시하면 완화되었습니다.이 경우 가상 방법을 호출하는 것이 완벽하게 안전합니다.

다른 팁

귀하의 질문에 답하기 위해이 질문을 고려하십시오. 아래 코드는 무엇을 인쇄 할 것인가 Child 객체가 인스턴스화 되었습니까?

class Parent
{
    public Parent()
    {
        DoSomething();
    }

    protected virtual void DoSomething() 
    {
    }
}

class Child : Parent
{
    private string foo;

    public Child() 
    { 
        foo = "HELLO"; 
    }

    protected override void DoSomething()
    {
        Console.WriteLine(foo.ToLower()); //NullReferenceException!?!
    }
}

대답은 사실 a입니다 NullReferenceException 때문에 던져 질 것입니다 foo NULL입니다. 객체의 기본 생성자가 자체 생성자 전에 호출됩니다.. a virtual 객체의 생성자에 호출하시는 객체가 완전히 초기화되기 전에 코드를 실행할 가능성을 소개합니다.

C#의 규칙은 Java 및 C ++의 규칙과 매우 다릅니다.

C#의 일부 객체의 생성자에있을 때, 해당 객체는 완전히 초기화 된 ( "구성") 양식으로 완전히 파생 된 유형으로 존재합니다.

namespace Demo
{
    class A 
    {
      public A()
      {
        System.Console.WriteLine("This is a {0},", this.GetType());
      }
    }

    class B : A
    {      
    }

    // . . .

    B b = new B(); // Output: "This is a Demo.B"
}

즉, A의 생성자에서 가상 함수를 호출하면 제공되는 경우 B의 재정의로 해결됩니다.

시스템의 동작을 완전히 이해하면서 의도적으로 A와 B를 설정하더라도 나중에 충격을받을 수 있습니다. B의 생성자에서 가상 함수를 불렀다. 그런 다음 시간이 지남에 따라 다른 사람이 C를 정의하고 일부 가상 기능을 무시해야한다고 결정합니다. 갑자기 B의 생성자는 모두 C로 코드를 호출하여 매우 놀라운 동작으로 이어질 수 있습니다.

규칙 이후 어쨌든 생성자의 가상 기능을 피하는 것이 좋습니다. ~이다 C#, C ++ 및 Java간에 다릅니다. 프로그래머는 무엇을 기대 해야할지 모를 수 있습니다!

경고의 이유는 이미 설명되었지만 경고를 어떻게 고치겠습니까? 클래스 또는 가상 멤버를 봉인해야합니다.

  class B
  {
    protected virtual void Foo() { }
  }

  class A : B
  {
    public A()
    {
      Foo(); // warning here
    }
  }

클래스 A를 봉인 할 수 있습니다.

  sealed class A : B
  {
    public A()
    {
      Foo(); // no warning
    }
  }

또는 방법 foo를 봉인 할 수 있습니다.

  class A : B
  {
    public A()
    {
      Foo(); // no warning
    }

    protected sealed override void Foo()
    {
      base.Foo();
    }
  }

C#에서 기본 클래스의 생성자가 실행됩니다. ~ 전에 파생 클래스의 생성자이므로, 파생 클래스가 가능하지 않은 가상 멤버에서 사용할 수있는 인스턴스 필드는 아직 초기화되지 않았습니다.

이것은 단지 a입니다 경고 주의를 기울이고 오른쪽인지 확인하십시오. 이 시나리오에 대한 실제 사용 사례가 있습니다. 행동을 문서화하십시오 가상 멤버 중 하나는 아래에서 파생 된 클래스에서 선언 된 인스턴스 필드를 사용할 수 없습니다.

왜 당신이 왜 당신을 잘 쓰여진 답변이 있습니다 그렇지 않을 것입니다 그렇게하고 싶어요. 아마 당신이 아마도 당신의 반례입니다 ~일 것이다 그렇게하고 싶다 (C#로 번역 루비의 실용적인 객체 지향 디자인 Sandi Metz, p. 126).

주목하십시오 GetDependency() 인스턴스 변수를 건드리지 않습니다. 정적 메소드가 가상 일 수 있다면 정적입니다.

(공정하게 말하면, 종속성 분사 컨테이너 또는 객체 이니셜 라이저를 통해이 작업을하는 더 똑똑한 방법이있을 것입니다 ...)

public class MyClass
{
    private IDependency _myDependency;

    public MyClass(IDependency someValue = null)
    {
        _myDependency = someValue ?? GetDependency();
    }

    // If this were static, it could not be overridden
    // as static methods cannot be virtual in C#.
    protected virtual IDependency GetDependency() 
    {
        return new SomeDependency();
    }
}

public class MySubClass : MyClass
{
    protected override IDependency GetDependency()
    {
        return new SomeOtherDependency();
    }
}

public interface IDependency  { }
public class SomeDependency : IDependency { }
public class SomeOtherDependency : IDependency { }

예, 생성자에서 가상 메소드를 호출하는 것은 일반적으로 나쁘다.

이 시점에서 OBJET은 아직 완전히 구성되지 않을 수 있으며 방법으로 예상되는 불변은 아직 유지되지 않을 수 있습니다.

생성자가 실행을 완료 할 때까지 객체가 완전히 인스턴스화되지 않습니다. 가상 함수에 의해 참조 된 모든 멤버는 초기화되지 않을 수 있습니다. C ++에서는 생성자에있을 때 this 생성중인 생성자의 정적 유형 만 말하며 생성중인 객체의 실제 동적 유형이 아닙니다. 이것은 가상 함수 호출이 당신이 기대하는 곳에 가지 않을 수도 있음을 의미합니다.

생성자는 (나중에, 소프트웨어 확장에서) 가상 메소드를 무시하는 서브 클래스의 생성자에서 호출 될 수 있습니다. 이제 서브 클래스의 기능 구현은 아니지만 기본 클래스의 구현이 호출됩니다. 따라서 여기서 가상 기능을 호출하는 것은 실제로 의미가 없습니다.

그러나 디자인이 Liskov 대체 원리를 만족 시키면 피해를 입지 않을 것입니다. 아마도 그것이 허용되는 이유 일 것입니다 - 오류가 아니라 경고입니다.

다른 답변이 아직 해결되지 않은이 질문의 한 가지 중요한 측면 중 하나는 기본 클래스가 생성자 내에서 가상 멤버를 호출하는 것이 안전하다는 것입니다. 그것이 파생 수업이 기대하는 것이라면. 그러한 경우, 파생 클래스의 설계자는 건설이 완료되기 전에 실행되는 모든 방법이 상황에서 가능한 한 현명하게 작동하도록 보장 할 책임이 있습니다. 예를 들어 C ++/CLI에서 생성자는 코드로 래핑되어 호출됩니다. Dispose 구조가 실패하면 부분적으로 구성된 물체에서. 부름 Dispose 그러한 경우 자원 누출을 방지하기 위해 종종 필요하지만 Dispose 실행되는 물체가 완전히 구성되지 않았을 가능성에 대한 방법은 준비되어야합니다.

경고는 가상 멤버가 파생 클래스에서 무시할 가능성이 있음을 상기시켜줍니다. 이 경우 학부모 수업이 가상 멤버에게 한 모든 일은 자식 수업을 재정의하여 취소하거나 변경됩니다. 명확성을 위해 작은 예제 타격을보십시오

아래의 상위 클래스는 생성자의 가상 멤버에게 가치를 설정하려고 시도합니다. 그리고 이것은 재시험가 경고를 유발하고 코드를 참조하십시오.

public class Parent
{
    public virtual object Obj{get;set;}
    public Parent()
    {
        // Re-sharper warning: this is open to change from 
        // inheriting class overriding virtual member
        this.Obj = new Object();
    }
}

여기서 어린이 수업은 부모 속성을 무시합니다. 이 속성이 가상으로 표시되지 않은 경우 컴파일러는 부동산이 상위 클래스의 속성을 숨기고 의도적 인 경우 '새로운'키워드를 추가 할 것을 제안합니다.

public class Child: Parent
{
    public Child():base()
    {
        this.Obj = "Something";
    }
    public override object Obj{get;set;}
}

마지막으로, 사용에 대한 영향, 아래 예제의 출력은 부모 클래스 생성자가 설정 한 초기 값을 포기합니다.그리고 이것은 당신에게 경고하려는 재평가가 시도하는 것입니다., 부모 클래스 생성자에 설정된 값은 부모 클래스 생성자 바로 다음에 호출되는 아동 클래스 생성자가 덮어 쓰기 위해 열려 있습니다..

public class Program
{
    public static void Main()
    {
        var child = new Child();
        // anything that is done on parent virtual member is destroyed
        Console.WriteLine(child.Obj);
        // Output: "Something"
    }
} 

Resharper의 조언을 따르고 수업을 봉인하는 것을 맹목적으로 조심하십시오! EF 코드의 모델 인 경우 먼저 가상 키워드를 제거하고 관계의 게으른로드가 비활성화됩니다.

    public **virtual** User User{ get; set; }

하나의 중요한 누락된 비트입니다,무엇이 올바른 방법으로 이 문제를 해결 하려면?

그렉 설명, 루트 문제가 여기에는 기본 클래스 구성자를 호출하는 가상 멤버 전에 파생 등으로 꾸며져 있습니다.

다음 코드에서 촬영 MSDN 의 생성자인,에서는 이 문제에 대해 설명합니다.

public class BadBaseClass
{
    protected string state;

    public BadBaseClass()
    {
        this.state = "BadBaseClass";
        this.DisplayState();
    }

    public virtual void DisplayState()
    {
    }
}

public class DerivedFromBad : BadBaseClass
{
    public DerivedFromBad()
    {
        this.state = "DerivedFromBad";
    }

    public override void DisplayState()
    {   
        Console.WriteLine(this.state);
    }
}

을 때의 새 인스턴스 DerivedFromBad 이 만든 베이스 클래스 구성자를 호출하여 DisplayState 과를 보여줍 BadBaseClass 기 때문에 필지에 의해 업데이트 파생 생성자입니다.

public class Tester
{
    public static void Main()
    {
        var bad = new DerivedFromBad();
    }
}

개선 구현을 제거 방법은 가상 기본 클래스에서 생성자 및 사용 Initialize 방법입니다.새 인스턴스를 만들의 DerivedFromBetter 표시 예상되는"DerivedFromBetter"

public class BetterBaseClass
{
    protected string state;

    public BetterBaseClass()
    {
        this.state = "BetterBaseClass";
        this.Initialize();
    }

    public void Initialize()
    {
        this.DisplayState();
    }

    public virtual void DisplayState()
    {
    }
}

public class DerivedFromBetter : BetterBaseClass
{
    public DerivedFromBetter()
    {
        this.state = "DerivedFromBetter";
    }

    public override void DisplayState()
    {
        Console.WriteLine(this.state);
    }
}

이 특정 경우에는 C ++와 C# 사이에 차이가 있습니다. C ++에서 물체는 초기화되지 않으므로 생성자 내부의 바이러스 기능을 호출하는 것은 안전하지 않습니다. C# 클래스 객체가 생성 된 경우 모든 멤버는 초기화됩니다. 생성자에서 가상 함수를 호출 할 수 있지만 여전히 0 인 멤버에 액세스 할 수 있습니다. 회원에 액세스 할 필요가 없다면 C#에서 가상 기능을 호출하는 것이 안전합니다.

내 생각을 더하기 위해. 정의 할 때 항상 개인 필드를 초기화하면이 문제를 피해야합니다. 최소한 아래 코드는 매력처럼 작동합니다.

class Parent
{
    public Parent()
    {
        DoSomething();
    }
    protected virtual void DoSomething()
    {
    }
}

class Child : Parent
{
    private string foo = "HELLO";
    public Child() { /*Originally foo initialized here. Removed.*/ }
    protected override void DoSomething()
    {
        Console.WriteLine(foo.ToLower());
    }
}

내가 찾은 또 다른 흥미로운 점은 아래에서 나에게 바보 같은 일을함으로써 Resharper 오류가 '만족'될 수 있다는 것입니다.

public class ConfigManager
{

   public virtual int MyPropOne { get; private set; }
   public virtual string MyPropTwo { get; private set; }

   public ConfigManager()
   {
    Setup();
   }

   private void Setup()
   {
    MyPropOne = 1;
    MyPropTwo = "test";
   }

}

기본 클래스에 초기화 () 메소드를 추가 한 다음 파생 생성자에서 호출합니다. 이 방법은 모든 생성자가 실행 된 후 모든 가상/초록 방법/속성을 호출합니다. :)

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top