Existential types in C#?
Asked Answered
L

4

15

I'm currently facing a problem in C# that I think could be solved using existential types. However, I don't really know if they can be created in C#, or simulated (using some other construct).

Basically I want to have some code like this:

public interface MyInterface<T>
{
    T GetSomething();
    void DoSomething(T something);
}

public class MyIntClass : MyInterface<int>
{
    int GetSomething()
    {
        return 42;
    }

    void DoSomething(int something)
    {
        Console.Write(something);
    }
}

public class MyStringClass : MyInterface<string>
{
    string GetSomething()
    {
        return "Something";
    }

    void DoSomething(string something)
    {
        SomeStaticClass.DoSomethingWithString(something);
    }
}

Next I want to be able to iterate through a list of objects that implement this interface, but without caring what type parameter it has. Something like this:

public static void DoALotOfThingsTwice(){
    var listOfThings = new List<MyInterface<T>>(){
        new MyIntClass(),
        new MyStringClass();
    };

    foreach (MyInterface<T> thingDoer in listOfThings){
        T something = thingDoer.GetSomething();
        thingDoer.DoSomething(something);
        thingDoer.DoSomething(something);
    }
}

This doesn't compile because the T used by MyIntClass and the one used by MyStringClass are different.

I was thinking that something like this could do the trick, but I don't know if there's a valid way to do so in C#:

public static void DoALotOfThingsTwice(){
    var listOfThings = new List<∃T.MyInterface<T>>(){
        new MyIntClass(),
        new MyStringClass();
    };

    foreach (∃T.MyInterface<T> thingDoer in listOfThings){
        T something = thingDoer.GetSomething();
        thingDoer.DoSomething(something);
        thingDoer.DoSomething(something);
    }
}
Liddell answered 9/9, 2015 at 18:12 Comment(2)
As the type is invariant, no, that's not possible.Charissacharisse
You could wrap the operation in an Action and then store those in a list. You can create those actions generically.Fruit
F
6

Since DoALotOfThingsTwice doesn't depend on T you can wrap it in an Action and store those in the list instead e.g.

public static Action DoSomethingTwice<T>(this MyInterface<T> i)
{
    return () =>
    {
        T something = i.GetSomething();
        i.DoSomething(something);
        i.DoSomething(something);
    };
}

then

var listOfThings = new List<Action>() {
    new MyIntClass().DoSomethingTwice(),
    new MyStringClass().DoSomethingTwice()
};
Fruit answered 9/9, 2015 at 18:25 Comment(2)
This would only work if I can do some additional work on that list, right? For example if DoSomethingTwice returned a value, making it Func<X> (for some X type), then it'd allow me to operate on the list using those X values (after evaluating each function). But if I don't do anything else other than DoSomethingTwice, then it'd be the same to just call DoSomethingTwice on each object separately, without using the list at all, right?Liddell
@Liddell - Well you can't call DoSomethingTwice on each element separately in your foreach loop because you can't construct the list you want in the first place. You need to construct some kind of wrapper at the point where you can abstract over T. If you need to return a value of T to be used inside your foreach loop then you have the same problem i.e. you can't describe T at all since C# doesn't support existential types as you want. However, what would you do with each T inside the loop? You can't meaningfully do anything without knowing T.Fruit
R
7

This is actually possible in a completely type-safe way and without any type casts and without any assumptions about what the interface functions do.

https://dotnetfiddle.net/buneul

using System;
using System.Collections;
using System.Collections.Generic;

public interface MyInterfaceFunc {
    void Call<T>(MyInterface<T> obj);
}

public interface MyInterface {
    void Generically(MyInterfaceFunc func);  // this is the key!
}

public interface MyInterface<T> : MyInterface
{
    T GetSomething();
    void DoSomething(T something);
}

public class MyIntClass : MyInterface<int>
{
    public int GetSomething()
    {
        return 42;
    }

    public void DoSomething(int something)
    {
        Console.Write(something);
    }

    public void Generically(MyInterfaceFunc func) {
        func.Call(this);
    }
}

public class MyStringClass : MyInterface<string>
{
    public string GetSomething()
    {
        return "Something";
    }

    public void DoSomething(string something)
    {
        Console.Write(something);
    }

    public void Generically(MyInterfaceFunc func) {
        func.Call(this);
    }
}
public class MyFunc : MyInterfaceFunc {
    public void Call<T>(MyInterface<T> thingDoer) {
        T something = thingDoer.GetSomething();
        thingDoer.DoSomething(something);
        thingDoer.DoSomething(something);
    }
}

public class Program {
    public static void Main(){
        var listOfThings = new List<MyInterface>(){
            new MyIntClass(),
            new MyStringClass()
        };

        foreach (MyInterface thingDoer in listOfThings){
            thingDoer.Generically(new MyFunc());
        }
    }
}
Rolland answered 16/2, 2019 at 0:29 Comment(0)
D
6

Not possible directly in C#.

You can either drop type safety and have non-generic base interface and use it for "generic" code:

public interface MyInterface
{
    object GetSomething();
    void DoSomething(object something);
}

public interface MyInterface<T> : MyInterface
{
    T GetSomething();
    void DoSomething(T something);
}

Or use dynamic (again no compile time type safety):

foreach (dynamic thingDoer in listOfThings)
{
    dynamic something = thingDoer.GetSomething();
    thingDoer.DoSomething(something);
    thingDoer.DoSomething(something);
}

Or generate multiple versions of the handler and create (possibly with caching) based on type (How do I use reflection to call a generic method?) (Note: that you can't really express "list of arbitrary objects" better than List<object> or List<NonGenericBaseInterface> or List<NonGenericBaseClass>):

foreach (object thingDoer in listOfThings)
{
   // get Do via reflection and create specific version based on 
   // thingDoer.GetType(), than invoke 
   // consider caching "methodForType" in Dictionary by type
   MethodInfo method = this.GetType().GetMethod("Do");
   MethodInfo methodForType = method.MakeGenericMethod(thingDoer.GetType());
   methodForType.Invoke(thingDoer, null);

}

void Do<T>( MyInterface<T> thingDoer)
{
    T something = thingDoer.GetSomething();
    thingDoer.DoSomething(something);
    thingDoer.DoSomething(something);
}

Alternative to reflection is to use Expression tree to build similar code.

Dallon answered 9/9, 2015 at 18:23 Comment(2)
Which one of those approaches would allow me to integrate it with LINQ? For example, if I extract the "generic" code to a function public static void DoSomethingTwice<T>(MyInterface<T> thingDoer), would one of your approaches allow me to map it using listOfthings.Select(DoSomethingTwice)? Seems like the approach using object would work for this, right?Liddell
@Liddell yes, non-generic interface would allow for some type safety (List<MyInterface>) and use linq listOfthings.Select(DoSomethingTwice), but it will not do dispatch on type of element - so DoSomethingTwice would have to work for all types or do dispatch on type in the code.Dallon
F
6

Since DoALotOfThingsTwice doesn't depend on T you can wrap it in an Action and store those in the list instead e.g.

public static Action DoSomethingTwice<T>(this MyInterface<T> i)
{
    return () =>
    {
        T something = i.GetSomething();
        i.DoSomething(something);
        i.DoSomething(something);
    };
}

then

var listOfThings = new List<Action>() {
    new MyIntClass().DoSomethingTwice(),
    new MyStringClass().DoSomethingTwice()
};
Fruit answered 9/9, 2015 at 18:25 Comment(2)
This would only work if I can do some additional work on that list, right? For example if DoSomethingTwice returned a value, making it Func<X> (for some X type), then it'd allow me to operate on the list using those X values (after evaluating each function). But if I don't do anything else other than DoSomethingTwice, then it'd be the same to just call DoSomethingTwice on each object separately, without using the list at all, right?Liddell
@Liddell - Well you can't call DoSomethingTwice on each element separately in your foreach loop because you can't construct the list you want in the first place. You need to construct some kind of wrapper at the point where you can abstract over T. If you need to return a value of T to be used inside your foreach loop then you have the same problem i.e. you can't describe T at all since C# doesn't support existential types as you want. However, what would you do with each T inside the loop? You can't meaningfully do anything without knowing T.Fruit
B
0

Since I don't know what's your underlying issue in your actual domain I can't provide a bullet-proof solution. It should be worth the effort that you should look about what's covariance and contravariance in generic parameters both on interfaces and delegates and give this approach a try refactoring your code.

For now, I believe that this should be a possible solution:

public static void DoALotOfThingsTwice()
{
    var listOfThings = new List<object>
    {
        new MyIntClass(), new MyStringClass()
    };

    MyInterface<int> a;
    MyInterface<string> b;

    // During each iteration, check if the thing is a concrete 
    // implementation of your interface MyInterface<T>...
    foreach (object thingDoer in listOfThings)
    {           
        // ...and call MyInterface<T>.DoSomething method depending on 
        // the success of the cast to MyInterface<int> or 
        // MyInterface<string> 
        if ((a = thingDoer as MyInterface<int>) != null)
            a.DoSomething(38);
        else if((b = thingDoer as MyInterface<string>) != null)
            b.DoSomething("hello world");
    }
}
Bevash answered 9/9, 2015 at 18:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.