Search code examples
c#spotfire

Getting hidden member from base class C#


In TIBCO Spotfire (I have no control over their code), there is an abstract class - DocumentNode which has a property Transactions implemented like so (according to disassembly):

public ITransactions Transactions
{
  [ApiVersion("2.0")] get => (ITransactions) this;
} 

So then DocumentNode implements

public interface ITransactions
{
    AggregatedTransactionHandle BeginAggregatedTransaction();

    void ExecuteInvisibleTransaction(Executor executor);

    void ExecuteStickyTransaction(Guid guid, Executor executor);

    void ExecuteTransaction( Executor executor);
}

where Executor is

delegate void Executor()

DocumentNode (again according to disassembly) implements the interface like so:

[ApiVersion("2.0")]
void ITransactions.ExecuteTransaction(Executor executor) => this.Transaction("Anonymous transaction", executor);

[ApiVersion("2.0")]
void ITransactions.ExecuteInvisibleTransaction(Executor executor) => this.InvisibleTransaction(executor);

[ApiVersion("2.0")]
void ITransactions.ExecuteStickyTransaction(Guid guid, Executor executor) => this.StickyTransaction(guid, executor);

[ApiVersion("2.0")]
public INodeContext Context
{
  [ApiVersion("2.0")] get => (INodeContext) this;
}

[ApiVersion("2.0")]
public ITransactions Transactions
{
  [ApiVersion("2.0")] get => (ITransactions) this;
}

But is only exposed via Transactions property and not directly via DocumentNode. The code I have SOME control over has many classes that inherit from DocumentNode and use ExecuteTransaction excessively causing nested transactions. Typical use:

this.Transactions.ExecuteTransaction(...

Internally Spotfire has some rules about what state an outer transaction can be in when inner one starts and it throws exceptions when the state does not match. I can't access that internal state, but I would be satisfied with a solution where if such Exception is caught, we run the executor without starting a transaction. I also need to keep the code changes to a minimum (I just do).

So I plan to make some of the classes that inherit from DocumentNode to instead inherit from DocumentNodeExt

public class DocumentNodeExt : DocumentNode
{
    public DocumentNodeExt() : base()
    { }

    public DocumentNodeExt(SerializationInfo info, StreamingContext context)
        : base(info, context)
    { }

    public new ITransactions Transactions
    {
        get { return this; }
    }


    public AggregatedTransactionHandle BeginAggregatedTransaction()
    {
        return base.Transactions.BeginAggregatedTransaction(); // stack overflow
        // return ((DocumentNode)this).Transactions.BeginAggregatedTransaction(); // stack overflow
    }

    public void ExecuteInvisibleTransaction(Executor executor)
    {
        try
        {
            base.Transactions.ExecuteInvisibleTransaction(executor);
        }
        catch (InvalidOperationException)
        {
            executor();
        }
    }

    public void ExecuteStickyTransaction(Guid guid, Executor executor)
    {
        base.Transactions.ExecuteStickyTransaction( guid,  executor);
    }

    void ExecuteTransaction(Executor executor)
    {
        try
        {
            base.Transactions.ExecuteTransaction(executor);
        }
        catch (InvalidOperationException)
        {
            executor();
        }
    }
}

And here is the problem: since DocumentNode.Transactions is not virtual, I can't override it in the derived class. I can only hide it via "new", but then how do I call DocumentNode's implementation of ExecuteTransaction? base does not have a method ExecuteTransaction and base.Transactions is the same as this.Transactions.

I could forget hiding Transactions and simply implement the ITransactions, but then I would also need to change the typical calls from

this.Transactions.ExecuteTransaction(... 

to

this.ExecuteTransaction(... 

which is more code changes than my "quota" allows.


Solution

  • You could only wrap the ITransactions member.

    public class DocumentNode : ITransactions
    {
        void ITransactions.ExecuteInvisibleTransaction() => Console.WriteLine("ExecuteInvisibleTransaction in DocumentNode");
        public ITransactions Transactions => this;
    }
    
    public class DocumentNodeExt : DocumentNode
    {
        public new ITransactions Transactions { get; }
        
        public DocumentNodeExt()
        {
            Transactions = new TransactionsWrapper(this);
        }
    }
    
    public class TransactionsWrapper : ITransactions
    {
        private readonly ITransactions _transactions;
        public TransactionsWrapper(ITransactions transactions)
        {
            _transactions = transactions;
        }
        
        public void ExecuteInvisibleTransaction()
        {
            Console.WriteLine("ExecuteInvisibleTransaction in TransactionsWrapper");
            _transactions.ExecuteInvisibleTransaction();
        }
    }
    
    public interface ITransactions
    {
        void ExecuteInvisibleTransaction();
    }
    

    https://dotnetfiddle.net/fzU3e1

    Notice that the DocumentNodeExt does NOT inherit the interface, only the wrapper for the ITransaction member does.

    Inheriting the same interface as the base class and calling a base member is what resulted in the stack overflow.

    Since I failed to understand why this stack overflow occured I have opened a seperate question for this

    Calling a base method throws stackoverflow if derived class inherits from same interface