Home > other >  How do I use a generic class without specifying its type parameters in C#?
How do I use a generic class without specifying its type parameters in C#?

Time:09-27

I'm aware that multiple similar sounding questions have been asked before, however I still believe that this particular problem has not been solved yet.

There is a class Super and a generic class Foo<T> : Super, which both come from a third-party library. I need to extend the functionality of Foo<T> so I have this generic class:

public class Bar<T, U> : Foo<T> {
  public void DoSomething() { ... }

  // Other things that use T and U
}

I now want to have a collection(list) of "Bar's" and be able to call DoSomething on all of them. So logically, I dont care about the type argument of Bar, when calling the void method as it is irrelevant in that scenario.

Additionally the third-party library exposes methods such as:

public LibraryMethod(Super super) { ... }

And I need to be able to call that method with any of my "Bar's".

Not-A-Solution 1: Interfaces

The reason why I cant use an interface is, because the third-party library expects a Super in some of the methods it exposes, and an interface can be implemented by any class, so I cannot use my Bar's as Super's even though every Bar (no matter what its type arguments are) must be a Super. The interface essentially ruins the inheritance chain here and I can no longer pass my "Bar's" to methods expecting a Super.

Not-A-Solution 2: Non-Generic Base Class

Obviously I cannot make Bar<T, U> inherit from a non-generic base class, as per the same reason as above. It would ruin the inheritance chain as well.

Fictional Ideal Situation

In an ideal situation I could do something like this:

List<Bar<>> bars = new List<Bar<>>();

bars.Add(new Bar<Type1, Type2>());
bars.Add(new Bar<Type3, Type4>());

foreach (Bar<> bar in bars) {
  bar.DoSomething();
}

LibraryMethod(bars[0]);

CodePudding user response:

You can add method AsSuper to your interface:

public interface IBar
{
   void DoSomething();
   Super AsSuper();
}

Which should be easily implemented in Bar:

public class Bar<T, U> : Foo<T>, IBar {
  public void DoSomething() { ... }
  public Super AsSuper() => this;
  // Other things that use T and U
}

Which will allow you to type safely invoke LibraryMethod:

LibraryMethod(bars[0].AsSuper());

CodePudding user response:

Simply put, you cannot do that easily in a strongly typed language such as C#.

Think of it this (simplified) way: a List<T> needs to know how big T in memory, so it can reserve enough space for its default number of entries. There is no guarantee to the compiler that Type1 and Type3 are the same size, nor Type2 and Type3.

CodePudding user response:

You can lose type information at compile time and track this elsewhere. Maybe you have program knowledge (you, the programmer) and know type information. Otherwise you have to create some data structure and carry this information around. Anyways, you can use interfaces here.

Consider your current situation (does not work):

public class Super { public int SuperData { get; set; } }
public class Foo<T> : Super { public T FooData { get; set; } }
public class Bar<T, U> : Foo<T>
{
    public U BarData { get; set; }
    public void DoSomething()
    {
        Console.WriteLine("i do something");
    }
}

public static void LibraryMethod(Super super) { Console.WriteLine("i'm super"); }
public void Main()
{
    // generic "object" collection
    var listy = new List<object>();
    listy.Add(new Bar<int, double>());
    listy.Add(new Bar<DateTime, bool>());
    
    foreach (var x in listy)
    {
        x.DoSomething(); /// 'object' does not contain a definition for 'DoSomething'

        // and then call your library
        LibraryMethod((Super)x);
    }
}

Instead, add an interface and implement in your type:

public interface IDoSomething
{
    void DoSomething();
}

public class Bar<T, U> : Foo<T>, IDoSomething
{
    public U BarData { get; set; }
    public void DoSomething()
    {
        Console.WriteLine("i do something");
    }
}

Now you can use with cast to correct interface type:

public void Main()
{
    var listy = new List<object>();
    listy.Add(new Bar<int, double>());
    listy.Add(new Bar<DateTime, bool>());
    
    foreach (var x in listy)
    {
        var bar = x as IDoSomething;
        // check null reference
        bar.DoSomething();

        // and then call your library
        LibraryMethod((Super)x);
    }
}

output:

> Main()
i do something
i'm super
i do something
i'm super

CodePudding user response:

You cannot do what you describe. replace your bar class by List or KeyValuePair to see what it result in:

var myList = new List<List<>>(); 
var myListOfKv = new List<KeyValuePair<>>(); 

you are trying to allocate some memory, the compiler needs to know some constraints. you can maybe try to put some restrictions on types like: class Bar<x,y> :Foo where x is ISoda

you might also think of another way to extend functionnality by adding in your children specific method to handle objects:

class Bar<x>: Foo<x>
{
    public void Inject(Iy iy);
    public void UseInjected();
}

The third party lib will still accept the Bar instances and ignore new functionnality.

  • Related