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.
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