0

I've been struggling for quite some time now with the following problem. Consider the following C# code:

public interface Foo<TD>
{
    List<TD> Bravo { get; set; }
    List<double> Yey { get; set; }
}

public class IS : Foo<int>
{
}

public class DS : Foo<double>
{
}

I have another generic class, say DE<TS>, where TS is constrained to be either IS or DS. A snippet of this class would be as follows:

public class DE<TS> // : where TD must be IS or DS...
{
    public void DoSomething(TS s)
    {
        // Some instruction that needs that need Foo<TD> properties to be visible...
    }
}

Implementing such a class may be "simple" using Java through the usage of generic wildcards. For instance, the DE class could have a signature DE<TS extends Foo<?>>. Among the related searches, this answer would be perfect if I did not need Foo<TD> properties.

How can I achieve my desired outcome in this situation?

José Joaquim
  • 103
  • 2
  • 9
  • Why is DE generic in the first place? Just make an `IS` and a `DS` overload of `DoSomething` ? – Fildor Jul 20 '22 at 20:26
  • Mind that they actually _do not_ implement the same interface. – Fildor Jul 20 '22 at 20:28
  • @Fildor class `DE` is intended to be a generic class in which `DoSomething` perform instructions that requires `Foo` properties. My snippet is just a subset of my program. Actually, I might have more classes implementing `Foo`. – José Joaquim Jul 20 '22 at 20:32
  • There are no `Foo` properties, because there is no `Foo`. There is only `Foo`. Which is _not_ one single interface, but a family of interfaces. So, if you want consumers to be agnostic of `T` then `Foo` needs to have methods that allow for that. – Fildor Jul 20 '22 at 20:33
  • Or inside `DoSomething` you could call methods that have overloads of every supported `T` in `List` ... then you can pass `s.Bravo` , does it make sense? – Fildor Jul 20 '22 at 20:39
  • @Fildor thanks for your perspective. I actually edit the question by changing `Foo` to `Foo`. Yes, it does make sense. It is a valid answer, actually. However, would not this workaround yield boilerplate code when `|T|` is large? – José Joaquim Jul 20 '22 at 20:47
  • Depends on T. I am having trouble to find words for what I have in my head ... – Fildor Jul 20 '22 at 20:50
  • Like: Have look at `List`. For all of what `List` provides in functionality, the _actual_ type of T is rather irrelevant. It can say "I have N Ts", it can add a T, remove a T ... T may be anything and the code would always be the same. You cannot really do that in your example, right? It _does_ matter what T actually is in `void DoSomething(Foo s)` because an `int` for `T` has another API than an `object` for `T`. You don't know what operators are defined on T ... – Fildor Jul 20 '22 at 20:58
  • And _if_ the actual type of T _does not_ matter for your code, then this might even be the solution: make it `DoSomething( Foo s )` instead of `DoSomething( TS s )` ? – Fildor Jul 20 '22 at 21:05
  • The type of `T` does not matter. However, I must be able to access the properties of `Foo` somehow. For instance, inside `DoSomething` I might wanna change the state of the `Bravo` object – José Joaquim Jul 20 '22 at 21:12
  • Your last comment gave me an ideia. What if `DE` where `TS` must be, considering the domain of my question, `int` or `double`, and `DoSomething` is implemented like `DoSomething(Foo s) where T : Foo`? – José Joaquim Jul 20 '22 at 21:14
  • I was just playing around: https://dotnetfiddle.net/ccmq1d - maybe you get an idea from it... – Fildor Jul 20 '22 at 21:51

1 Answers1

2

Just pull Foo generic type parameter to DE's declaration, like this:

public class DE<TS, TD> where TS : Foo<TD>
Ondrej Tucny
  • 27,626
  • 6
  • 70
  • 90
  • Would this solution yield "duplicated" information? For instance, `DE where IS : Foo` seems obvious since `IS` is indeed (and can only be) `Foo` – José Joaquim Jul 20 '22 at 20:24
  • @JoséJoaquim You don't specify the `where` when you actually use it. You could just do `new DE()`.. In fact, you can avoid any generic parameters at all if you have a generic factory method `DE CreateDE where TS : Foo() => new DE();` – Charlieface Jul 20 '22 at 20:28
  • 1
    @Charlieface I am still wondering: What do you win? You still need to know what TD _actually_ is. At compile-time. And while in OP's example, it's just `int` and `double`, it could still be `object`, `string`, `MyCustomClass` ... so what does it help for `DoSomething( TS s )`? – Fildor Jul 20 '22 at 20:45
  • 2
    @Fildor Sorry I meant something like `DE CreateDE where TS : Foo(TD someValue) => new DE(someValue);` where you should be able to call it `CreateDE(someInt)` and not use any generic parameters (inference) – Charlieface Jul 20 '22 at 20:49