问题 C#中的存在类型?


我目前正面临C#中的问题,我认为可以使用存在类型来解决。但是,我真的不知道它们是可以用C#创建还是模拟(使用其他一些构造)。

基本上我想要一些像这样的代码:

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);
    }
}

接下来,我希望能够遍历实现此接口的对象列表,但不关心它具有什么类型参数。像这样的东西:

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);
    }
}

这不编译,因为 T 用于 MyIntClass 和使用的那个 MyStringClass 是不同的。

我认为像这样的东西可以解决问题,但我不知道在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);
    }
}

10921
2017-09-09 18:12


起源

由于类型是不变的,不,这是不可能的。 - Servy
你可以把操作包装成一个 Action 然后将它们存储在列表中。您可以通常创建这些操作。 - Lee


答案:


以来 DoALotOfThingsTwice 不依赖于 T 你可以将它包装成一个 Action 并将其存储在列表中,例如

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

然后

var listOfThings = new List<Action>() {
    new MyIntClass().DoSomethingTwice(),
    new MyStringClass().DoSomethingTwice()
};

5
2017-09-09 18:25



这只有在我可以在该列表上做一些额外的工作时才有效,对吧?例如,如果 DoSomethingTwice 返回一个值,制作它 Func<X> (对于一些 X 类型),然后它允许我使用这些操作列表 X 值(在评估每个函数之后)。但如果我除了以外什么都不做 DoSomethingTwice那么打电话就是一样的 DoSomethingTwice 分别在每个对象上,根本不使用列表,对吧? - gonzaw
@gonzaw - 你不能打电话 DoSomethingTwice 在你的每个元素分别 foreach 循环,因为您无法首先构建所需的列表。你需要在可以抽象的地方构造某种包装器 T。如果需要返回值 T 在你的内心使用 foreach 循环然后你有同样的问题,即你无法描述 T 因为C#不支持您想要的存在类型。但是,你会对每个人做些什么 T 在循环内?没有知道,你无法做任何有意义的事情 T。 - Lee


不可能直接在C#中。

您可以删除类型安全性并具有非通用基本接口,并将其用于“通用”代码:

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

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

或者使用 dynamic (再次没有编译时类型安全):

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

或者生成多个版本的处理程序并根据类型创建(可能使用缓存)(如何使用反射来调用泛型方法?)(注意:你不能真正表达“任意对象列表”比 List<object> 要么 List<NonGenericBaseInterface> 要么 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);
}

反射的替代方法是使用表达式树来构建类似的代码。


4
2017-09-09 18:23



哪种方法可以让我将它与LINQ集成?例如,如果我将“通用”代码提取到函数中 public static void DoSomethingTwice<T>(MyInterface<T> thingDoer),您的一种方法是否允许我使用它进行映射 listOfthings.Select(DoSomethingTwice)?似乎使用的方法 object 会为此工作,对吧? - gonzaw
@gonzaw是的,非通用接口允许某些类型安全(List<MyInterface>)并使用linq listOfthings.Select(DoSomethingTwice),但它不会对元素类型进行调度 - 所以 DoSomethingTwice 必须适用于所有类型或在代码中调度类型。 - Alexei Levenkov


由于我不知道您在实际域中的根本问题是什么,因此我无法提供防弹解决方案。应该值得你去看看的努力 接口和委托中通用参数的协方差和反演是什么 并尝试重构您的代码。

目前,我认为这应该是一个可能的解决方案:

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");
    }
}

1
2017-09-09 18:50