0

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.

ILIA BROUDNO
  • 1,539
  • 17
  • 24

2 Answers2

1

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

Rand Random
  • 7,300
  • 10
  • 40
  • 88
  • @ILIABROUDNO - you could overcome this by using a proxy eg. https://stackoverflow.com/questions/8387004/how-to-make-a-simple-dynamic-proxy-in-c-sharp – Rand Random May 04 '21 at 16:16
  • Thank you Rand Random, I have not tried that (proxy for all the calls I don't override), but I am not really tempted - putting every call behind a proxy would be a pretty serious impact - there are A LOT of them in this vast ocean of code and some of them likely use other tricks to achieve their own goals that are not going to be compatible with a proxy. Plus I doubt it would leave your call stack nice and meaningful while debugging. – ILIA BROUDNO May 04 '21 at 16:41
  • Aha! Yes that last one https://dotnetfiddle.net/fzU3e1 seems to be the solution. Thank you Rand Random. I'm marking it as answer. Do you want to leave it as is or do you want to to clean up that answer so only the last part is visible to whomever is going in for the first time, I'd be happy to remove the comments so it's all nice and tidy. – ILIA BROUDNO May 04 '21 at 20:07
  • @ILIABROUDNO I plan to clean it up, but that won’t happen till tomorrow. Need sleep. :) FYI I was curious why the stackoverflow only happens if documentnodeext inherits the interface and asked about this here - https://stackoverflow.com/questions/67390405 – Rand Random May 04 '21 at 20:16
0

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

I don't see why. Let's establish a few things:

  1. this is of some type that inherits from DocumentNode.
  2. DocumentNode implements ITransaction.
  3. Transactions is non-virtual.
  4. Transactions is defined in the base DocumentNode as (ITransaction) this.

From 1., 2. and 4. it follows that for DocumentNode's implementation of Transactions the following is always true:

this.ReferenceEquals(this.Transactions)

Because of 4. it must be true in all subclasses that don't hide Transactions, since they cannot override it. So the calls to this.ExecuteTransaction and this.Transactions.ExecuteTransaction must be the same as long as you're not doing any weird hiding in the subclasses.

To put this into code, consider this stripped down version of your code:

public interface ITransactions
{
    void ExecuteTransaction();
}

public class DocumentNode : ITransactions
{
    void ITransactions.ExecuteTransaction() => Console.WriteLine("Base ExecuteTransaction");
    
    public ITransactions Transactions => (ITransactions) this;
}

public class ExtDocumentNode : DocumentNode, ITransactions
{
    public void ExecuteTransaction() => Console.WriteLine("Overriden ExecuteTransaction");
}

public class Implementation : DocumentNode
{
    public void Foo() => this.Transactions.ExecuteTransaction();
}

public class ExtImplementation : ExtDocumentNode
{    
    public void Foo() => this.Transactions.ExecuteTransaction();
}

Now the following code:

var instance = new Implementation();
var extInstance = new ExtImplementation();

instance.Foo();
extInstance.Foo();

prints

Base ExecuteTransaction
Overriden ExecuteTransaction

as expected (see demo).

So, as stated, I either completely missed the point or your problem is not a problem. Please correct me if I'm wrong.

V0ldek
  • 9,623
  • 1
  • 26
  • 57
  • I modified your code to show OPs issue, atleast how I understood it - https://dotnetfiddle.net/hN1nMl - by casting `ExtImplementation` to the base class `DocumentNode` it is calling the base classes method and not the one marked with `new` - the problem wouldn't exist if the method would have been virtual and could be overwritten – Rand Random May 04 '21 at 12:38
  • Thank you for the effort V0ldek. The part you missed is where ExtDocumentNode implementation of ExecuteTransaction has to call DocumentNode's one inside of it as my code shows above : base.Transactions.ExecuteTransaction(executor); which does not actually work, but tells you I want to call the base one. – ILIA BROUDNO May 04 '21 at 16:08
  • Rand Random, thank you for the casting idea. I did try that before asking the question, but it did not work for me. I suspect because I had to call the "base" implementation of ExecuteTransaction inside the derived implementation of ExecuteTransactions AND I had to call it via Transaction property as ExecuteTransaction method is NOT public in my code. Which explains why I am NOT hiding the methods but instead hiding the property. – ILIA BROUDNO May 04 '21 at 16:21
  • Rand Random I added to your and V0ldek's code to illustrate what I said above https://dotnetfiddle.net/domK4K – ILIA BROUDNO May 04 '21 at 17:12
  • @ILIABROUDNO - to overcome the stackoverflow you would need to cast it like this `((DocumentNode)this.Transactions).ExecuteTransaction();` – Rand Random May 04 '21 at 17:22
  • Rand Random and V0ldek, the thing that is missing in your simplification is that DocumentNode does NOT expose ExecuteTransaction. I added that wrinkle here: https://dotnetfiddle.net/CqMcBA. P.S. I definitely learned something new through this discussion thanks to you. – ILIA BROUDNO May 04 '21 at 18:03