Как получить доступ к закрытым членам суперкласса в переопределении?

Я хочу унаследовать от класса NHibernate SqlClientBatchingBatcher точно так же (код взят из TooManyRowsAffectedException с зашифрованным триггером) ):

public class NonBatchingBatcherWithoutVerification : SqlClientBatchingBatcher
{
    public NonBatchingBatcherWithoutVerification(ConnectionManager connectionManager, IInterceptor interceptor) : base(connectionManager, interceptor)
    {}

    protected override void DoExecuteBatch(IDbCommand ps)
    {
        log.DebugFormat("Executing batch");
        CheckReaders();
        Prepare(currentBatch.BatchCommand);
        if (Factory.Settings.SqlStatementLogger.IsDebugEnabled)
        {
            Factory.Settings.SqlStatementLogger.LogBatchCommand(currentBatchCommandsLog.ToString());
            currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:");
        }

        int rowsAffected = currentBatch.ExecuteNonQuery();

        // Removed the following line
        //Expectations.VerifyOutcomeBatched(totalExpectedRowsAffected, rowsAffected);

        currentBatch.Dispose();
        totalExpectedRowsAffected = 0;
        currentBatch = new SqlClientSqlCommandSet();
    }
}

Просто обратите внимание на некоторые элементы, к которым осуществляется доступ в этом методе (например, currentBatch или totalExpectedRowsAffected).

Что ж, оказывается, эти члены на самом деле являются частными в суперклассе текущего исходного кода NHibernate 3.3. Итак, как мне эффективно унаследовать класс, не копируя его целиком? Между прочим, это неизмененный код класса NHibernate:

 public class SqlClientBatchingBatcher : AbstractBatcher
{
    private int _batchSize;
    private int _totalExpectedRowsAffected;
    private SqlClientSqlCommandSet _currentBatch;
    private StringBuilder _currentBatchCommandsLog;
    private readonly int _defaultTimeout;

    public SqlClientBatchingBatcher(ConnectionManager connectionManager, IInterceptor interceptor)
        : base(connectionManager, interceptor)
    {
        _batchSize = Factory.Settings.AdoBatchSize;
        _defaultTimeout = PropertiesHelper.GetInt32(Cfg.Environment.CommandTimeout, Cfg.Environment.Properties, -1);

        _currentBatch = CreateConfiguredBatch();
        //we always create this, because we need to deal with a scenario in which
        //the user change the logging configuration at runtime. Trying to put this
        //behind an if(log.IsDebugEnabled) will cause a null reference exception 
        //at that point.
        _currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:");
    }

    public override int BatchSize
    {
        get { return _batchSize; }
        set { _batchSize = value; }
    }

    protected override int CountOfStatementsInCurrentBatch
    {
        get { return _currentBatch.CountOfCommands; }
    }

    public override void AddToBatch(IExpectation expectation)
    {
        _totalExpectedRowsAffected += expectation.ExpectedRowCount;
        IDbCommand batchUpdate = CurrentCommand;
        Driver.AdjustCommand(batchUpdate);
        string lineWithParameters = null;
        var sqlStatementLogger = Factory.Settings.SqlStatementLogger;
        if (sqlStatementLogger.IsDebugEnabled || Log.IsDebugEnabled)
        {
            lineWithParameters = sqlStatementLogger.GetCommandLineWithParameters(batchUpdate);
            var formatStyle = sqlStatementLogger.DetermineActualStyle(FormatStyle.Basic);
            lineWithParameters = formatStyle.Formatter.Format(lineWithParameters);
            _currentBatchCommandsLog.Append("command ")
                .Append(_currentBatch.CountOfCommands)
                .Append(":")
                .AppendLine(lineWithParameters);
        }
        if (Log.IsDebugEnabled)
        {
            Log.Debug("Adding to batch:" + lineWithParameters);
        }
        _currentBatch.Append((System.Data.SqlClient.SqlCommand) batchUpdate);

        if (_currentBatch.CountOfCommands >= _batchSize)
        {
            ExecuteBatchWithTiming(batchUpdate);
        }
    }

    protected override void DoExecuteBatch(IDbCommand ps)
    {
        Log.DebugFormat("Executing batch");
        CheckReaders();
        Prepare(_currentBatch.BatchCommand);
        if (Factory.Settings.SqlStatementLogger.IsDebugEnabled)
        {
            Factory.Settings.SqlStatementLogger.LogBatchCommand(_currentBatchCommandsLog.ToString());
            _currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:");
        }

        int rowsAffected;
        try
        {
            rowsAffected = _currentBatch.ExecuteNonQuery();
        }
        catch (DbException e)
        {
            throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not execute batch command.");
        }

        Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, rowsAffected);

        _currentBatch.Dispose();
        _totalExpectedRowsAffected = 0;
        _currentBatch = CreateConfiguredBatch();
    }

    private SqlClientSqlCommandSet CreateConfiguredBatch()
    {
        var result = new SqlClientSqlCommandSet();
        if (_defaultTimeout > 0)
        {
            try
            {
                result.CommandTimeout = _defaultTimeout;
            }
            catch (Exception e)
            {
                if (Log.IsWarnEnabled)
                {
                    Log.Warn(e.ToString());
                }
            }
        }

        return result;
    }
}

Я что-то упустил? Кажется, довольно плохой подход - копировать все это просто для отмены любого доступа к любым частным членам. Я просто хочу изменить один метод!


person cdbeelala89    schedule 20.03.2013    source источник


Ответы (4)


Есть только один способ легально получить доступ к частным членам вашего базового класса: поместить производный класс внутри базового класса:

class Base
{
    private int x;
    private class Derived : Base
    {
        private void M()
        {
            Console.WriteLine(this.x); // legal!
        }
    }
}

Конечно, если бы вы могли поместить класс в базовый класс, вы также могли бы переписать базовый класс, чтобы члены были защищены.

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

person Eric Lippert    schedule 20.03.2013

Если они установлены как частные, вы действительно ничего не можете сделать (кроме использования Reflection, что некрасиво и, конечно, не всегда безопасно).

person Justin Niessner    schedule 20.03.2013

Private члены суперкласса недоступны, потому что они private. Инкапсуляция в ООП предназначена для запрета этого прямого доступа и обеспечения правильного функционирования объектов.
Может быть properties для доступа к закрытым членам. Это те, которые вы можете использовать для чтения / записи частным членам. Свойства гарантируют, что объекту не будет нанесено никакого вреда.

person bash.d    schedule 20.03.2013
comment
Почему бы вам воздержаться от использования свойств, которые используют частные резервные переменные? - person Blorgbeard; 21.03.2013

Вы можете получить доступ к закрытым полям, свойствам и методам родительского класса, используя отражение (например, доступ к полю, как описано здесь: Отражение частного поля из базового класса)

Это небезопасно, однако идея частного заключается в том, что реализация библиотеки может измениться, а эти частные методы, поля и свойства могут измениться или исчезнуть. Если они изменят реализацию, обновление может сломать ваш код.

Тем не менее, я сам делал это несколько раз. Вам просто нужно взвесить риск.

person Pete    schedule 20.03.2013