C#构造函数中的虚方法调用 - 如何重构?

时间:2012-10-03 15:28:05

标签: c# .net

我有一个用于数据库无关的游标操作的抽象类。 从中衍生出来,有些类实现了处理特定于数据库的东西的抽象方法。

问题是,基类ctor需要调用抽象方法 - 当调用ctor时,它需要初始化特定于数据库的游标。

我知道为什么不应该这样做,我不需要那个解释!

这是我的第一个实现,显然不起作用 - 这是教科书“错误的方式”。 重写的方法访问派生类中的一个字段,该字段尚未实例化:

public abstract class CursorReader
{
    private readonly int m_rowCount;
    protected CursorReader(string sqlCmd)
    {
         m_rowCount = CreateCursor(sqlCmd); //virtual call !
    }
    protected abstract int CreateCursor(string sqlCmd);

    //...other (non-abstract) methods that assume a cursor exists
}

public class SqlCursorReader : CursorReader
{
    private SqlConnection m_sqlConnection;

    public SqlCursorReader(string sqlCmd, SqlConnection sqlConnection)
    {
        m_sqlConnection = sqlConnection;     //field initialized here
    }
    protected override int CreateCursor(string sqlCmd)
    {
        //uses not-yet-initialized member *m_sqlConnection*
        //so this throws a NullReferenceException
        var cursor = new SqlCursor(sqlCmd, m_sqlConnection); 
        cursor.Create();
        return cursor.Count();
    }
}

我将跟进我的解决方案,以解决这个问题......

更新

重写方法CreateCursor()在数据库中创建实际游标。这对于从班级中省略的许多方法的正确运作至关重要 必须在基本ctor中调用CreateCursor(),以便在ctor返回时类处于一致状态。我稍微更新了上面的代码以反映这一点。

5 个答案:

答案 0 :(得分:4)

你总是可以拥有一个可以计算的懒惰属性。

public abstract class CursorReader
{
    private int? m_rowCount;
    protected CursorReader()
    {

    }
    protected abstract int CreateCursor(string sqlCmd);
    protected int RowCount {
      get {
          if (m_RowCount == null)
          {
             m_RowCount = CreateCursor(sql);
          }
          return m_RowCount.Value;
      }

    }
}

答案 1 :(得分:1)

怎么样:

public abstract class CursorReader
{
    private int? m_rowCount = null;
    private int rowCount { get { return m_rowCount = m_rowCount ?? CreateCursor(sqlCmd); } }
    protected CursorReader() { }
    protected abstract int CreateCursor(string sqlCmd);
}

答案 2 :(得分:1)

您可能需要将逻辑分离为构造函数和初始化。

public abstract class CursorReader
{
    private readonly int m_rowCount;
    protected CursorReader()
    {

    }

    protected void Init()
    {
         m_rowCount = CreateCursor(sqlCmd); //virtual call !
    }

    protected abstract int CreateCursor(string sqlCmd);
}

这需要您在每个新实例上调用Init(),但这是我能想到的最佳解决方案。

请注意,Init可以像您提到的那样从派生类中调用,但我认为从调用代码调用它会更简单。有很多类型使用这种模式,即使它需要更多代码,但这并不是一种不好的做法。

答案 3 :(得分:1)

这是我正在考虑的第二个方向:

为了解决鸡与蛋的问题,同时允许热切地创建游标,一些抽象是有序的。

在基类中,虚拟调用尝试访问派生类中尚未初始化的字段。因此,让我们提取在不同类中创建游标的功能。基类对它的创建方式不感兴趣,它只是更大算法中的一步。

对我来说,这种方法的要点与strategy pattern类似 - 主类知道算法的一般步骤,而步骤的实际实现细节在运行时插入。

public interface ICursorCreator {
    int CreateCursor(string sqlCmd);
}
public abstract class CursorReader
{
    private readonly int m_rowCount;
    protected CursorReader(string sqlCmd, ICursorCreator creator)
    {
         m_rowCount = creator.CreateCursor(sqlCmd); //no longer a virtual call 
    }
    //protected abstract int CreateCursor(string sqlCmd);//no longer needed

    //...other (non-abstract) methods that assume a cursor exists
}

//move the logic of creating a cursor in a separate class, and pass an instance of that to the base class. 
public SqlCursorCreator: ICursorCreator {
    private SqlConnection m_sqlConnection;
    public SqlCursorCreator(SqConnection conn){
        m_sqlConnection = conn;
    }
    public int CreateCursor(string sqlCmd)
    {
        var cursor = new SqlCursor(sqlCmd, m_sqlConnection); 
        cursor.Create();
        return cursor.Count();
    }
}

public class SqlCursorReader : CursorReader
{
    //private SqlConnection m_sqlConnection;//no longer needed

    //by saving the connection in the factory, it will be available when needed later
    public SqlCursorReader(string sqlCmd, SqlConnection sqlConnection)
        :this(sqlCmd, new SqlCursorCreator(sqlConnection))
    { }
    protected SqlCursorReader(string sqlCmd, SqlCursorCreator creator)
        : base(sqlCmd, creator)
    { }
}

答案 4 :(得分:0)

第一次尝试是将虚拟呼叫从基本ctor中移出,但它有一些缺点:

  • m_rowCount不再是只读
  • 派生类AND ALL FUTURE DERIVED CLASSES需要调用基类Initialize()方法。

-

public abstract class CursorReader
{
    private int m_rowCount;//no longer read-only
    protected CursorReader()
    {
        //no virtual call here
    }
    protected abstract int CreateCursor(string sqlCmd);
    protected void Initialize()
    {
         //virtual call moved here
         m_rowCount = CreateCursor(sqlCmd); //virtual call !
    }
}
public class SqlCursorReader : CursorReader
{
    private SqlConnection m_sqlConnection;

    public SqlCursorReader(string sqlCmd, SqlConnection sqlConnection)
    {
        m_sqlConnection = sqlConnection;

        //the derived classes NEED to call the base class' Initialize() 
        Initialize();
    }
    protected override int CreateCursor(string sqlCmd)
    {
        //uses not-yet-initialized member m_sqlConnection
        var cursor = new CustomCursor(sqlCmd, m_sqlConnection); 
        return cursor.Count();
    }
}

我特别不喜欢第二颗子弹......