Search code examples
c#.netdatatabletableadapter

General Method for auto-generated classes


Context:

I'm working on a .NET C# application in Visual Studio.
I discovered DataSets to interact with my DataBase, so I use TableAdapters. Each Time I add a new TableAdapter associated with a table, the designer create several specific classes. If I create a TableApdapter for the Table 'myTable', the designer would create the class MyTableTableAdapter, inherited from global::system.Component, and the class MyTableDataTable, inherited from DataTable.

Problem:

I created a custom DataGridView that have the following attribute:

private System.ComponentModel.Component tableAdapter;

My component should be able to manipulate any TableAdapter created by the designer.
I will now simple the problem:

//Auto-generated by the designer
public partial class ATableAdapter : global::system.Component
{
    public virtual ADataTable GetData()
    {
        ADataTable aDataTable = new ADataTable();
        // Do things
        return aDataTable;
    }
}

//Auto-generated by the designer
public partial class BTableAdapter : global::system.Component
{
    public virtual BDataTable GetData()
    {
        ADataTable bDataTable = new BDataTable();
        // Do things
        return bDataTable;
    }
}

// My code
public class TableAdapterManager
{
    private Component tableAdapter;
    
    public TableAdapterManager(Component tableAdapter)
    {
        this.tableAdapter = tableAdapter;
    }
    
    // What I want to do
    public DataTable GetData()
    {
        return tableAdapter.GetData();
    }
}

The main problem emerge :
the class Component doesn't implement any definition for public DataTable GetData().
I need to get the DataTable returned from the method GetData(), no matter the type of TableAdapter. The thing is: I don't want to modify auto-generated code. I modified it once, and the designer overwrote it.

So far, I thought about Interfaces :

public interface IDataProvider
{
    DataTable GetData();
}

As auto-generated classes are partial, I decided to code another part of each class:

public partial class ATableAdapter : IDataProvider
{
    public DataTable GetData() // In respect to the interface
    {
        ADataTable aDataTable = new ADataTable();
        // Do exact same things as in the original definition but with DataTable parent type
        return adataTable;
    }
}

public partial class BTableAdapter : IDataProvider
{
    public DataTable GetData() // In respect to the interface
    {
        BDataTable bDataTable = new BDataTable();
        // Do exact same things as in the original definition but with DataTable parent type
        return bDataTable;
    }
}

// My code
public class TableAdapterManager
{
    private Component tableAdapter;
    
    public TableAdapterManager(Component tableAdapter)
    {
        this.tableAdapter = tableAdapter;
    }
    
    // What I want to do
    public DataTable GetData()
    {
        return ((IDataProvider)tableAdapter).GetData();
    }
}

It seemed correct, but I got that error:

Error   CS0111  Type 'ATableAdapter' already defines a member called 'GetData' with the same parameter types.
Error   CS0111  Type 'BTableAdapter' already defines a member called 'GetData' with the same parameter types.
C:\Path\ProjectDataSet.Designer.cs

I thought the Interface approach was consistent but I don't see any other solution.
An easy solution would be to name the interface's method slightly different, but it would it wouldn't be as rigorous.


Solution

  • There are two possible quick solutions:

    1. Use explicit interface implementation
    2. Use generic type parameter variance

    1. Using explicit interface implementation:

    Simply change your hand-written partial types such that the GetData() methods are explicit implementations of IDataProvider:

    public partial class ATableAdapter : IDataProvider
    {
        DataTable IDataProvider.GetData() => this.GetData();
    }
    

    This works because:

    • The this.GetData() call-sites will always resolve to the public virtual BDataTable GetData() methods in the codegen'd file (as this. cannot be used to dereference explicit interface implementation members).
    • ...and because this.GetData() returns BDataTable which derives from DataTable which means it can be returned from DataTable IDataProvider.GetData() as-is.

    This way, your TableAdapterManager (which sounds rather Enterprise-y) code will work using your current return ((IDataProvider)tableAdapter).GetData(); statement.

    2. Use generic type parameter variance:

    Assuming you're using C# 8 (or 9?) or later, then extend your IDataProvider with Default Implementation Members and Generic Type Parameter variance, like so:

    public interface IDataProvider
    {
        public abstract DataTable GetData();
    }
    
    public interface IDataProvider<out TDataTable> : IDataProvider
        where TDataTable : DataTable
    {
        public new abstract TDataTable GetData();
        
        DataTable IDataProvider.GetData() => this.GetData();
    }
    

    ...this way you don't need any hand-written partial types except to add the IDataProvider<out TDataTable> bit:

    // Auto-generated by the designer:
    public partial class ATableAdapter : global::system.Component
    {
        public virtual ADataTable GetData()
        {
            ADataTable aDataTable = new ADataTable();
            // Do things
            return aDataTable;
        }
    }
    
    // Hand-written:
    public partial class ATableAdapter : IDataProvider<ADataTable>
    {
    }