【问题标题】:How can a `System.Type` be a generic parameter with constraints?`System.Type` 如何成为具有约束的通用参数?
【发布时间】:2014-11-10 20:00:23
【问题描述】:

System.Type 如何成为具有约束的通用参数?

这里的接口I 有一个方法M,类CD 实现。

public interface I {
    void M;
}

public class C: I {
    public void M();
}
public class D: I {
    public void M();
}

Service 实例化一个新的 T()(实际上是 C/D 实现 I)并在其上运行 I 接口方法

public class Service {
    public void Fn<T>(T t): where T : Type, I, new() {
        ( new t() ).M();
        // ...
    }
}

这最终将成为服务的公共 API,因此我希望它尽可能简洁:

public class Main {
    public void main() {
        Service.Fn( typeof( C ));
        Service.Fn( typeof( D ));
        // ...
    }
}

这不是编译,错误是: 'System.Type' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'T' in the generic type or method ...

这里的最终目标是能够简洁地调用Service.Fn,如Main 中所述。也欢迎我的接口尝试的任何替代方案。

【问题讨论】:

  • 您是否要创建C/D?如果是这样,您应该使用通用参数,例如 Service.Fn&lt;C&gt;()
  • C 和 D 类型,但它们不继承 Type。此外,您似乎不了解类型对象(例如,typeof(C))和类型标记(例如,Service.Fn&lt;C&gt;())之间的区别
  • 除了语法之外,类型对象和类型标记有什么区别?

标签: c# generics types interface constraints


【解决方案1】:

以下代码适用于我:

public class Service {
    public void Fn<T>() where T : I, new() {
        (new T()).M();
        // ...
    }
}
public interface I
{
    void M();
}

public class C : I
{
    public void M();
}
public class D : I
{
    public void M();
}

static void Main()
{
    var service = new Service();
    service.Fn<C>();
    service.Fn<D>();
}

问题在于where 子句中多余的TypeT 无论如何都是一个类型,因为它是一个通用参数 - 但 AB 类型的对象不能转换为 Type 类 - 这就是 where T : Type 的意思。

一般来说,where T: X, Y, Z 意味着任何T 必须(取决于XYZ 是什么)要么实现接口,要么是X、@987654337 的子类@ 和 Znew() 约束略有不同,它使编译器知道您还希望能够在方法中创建 T 类型的新对象。 (详情请参阅http://msdn.microsoft.com/en-gb/library/bb384067.aspxhttp://msdn.microsoft.com/en-gb/library/d5x73970.aspx

另外,我已经从您的输入中删除了typeOf(),因为当您使用泛型时,可以在函数中直接引用该类型(见上文)。希望这是有道理的。有任何问题,尽管问!

【讨论】:

    【解决方案2】:

    这个例子可以帮助你理解这里发生了什么:

    void PrintTypeName(Type t) { Console.WriteLine(t.Name); }
    void PrintTypeName<T>() { Console.WriteLine(typeof(T).Name); }
    
    void Main()
    {
        Type cType = typeof(C);
        Type dType = typeof(D);
        C cInstance = new C();
        D dInstance = new D();
    
        PrintNameOfType(cType.GetType());     //prints "RuntimeType"
        PrintNameOfType(dType.GetType());     //prints "RuntimeType"
        PrintNameOfType(cInstance.GetType()); //prints "C"
        PrintNameOfType(dInstance.GetType()); //prints "D"
    
        PrintNameOfType(cType);     //prints "C"
        PrintNameOfType(dType);     //prints "D"
    
        //does not compile:
        //PrintNameOfType(cInstance);
        //PrintNameOfType(dInstance);
    
        PrintNameOfType<Type>(); //prints "Type"
        PrintNameOfType<C>();    //prints "C"
        PrintNameOfType<D>();    //prints "D"
    }
    

    【讨论】:

    • 很棒的例子 phoog!如果可以,我将添加一个有助于我了解差异的有用概念:T 可以被认为是替换任何其他类型,例如 objectstring 等,而 typeof(T) 是一种将类型作为变量传递。
    • @DavidEdey 感谢您的评论。我更喜欢说“将类型作为对象传递”,但这可能令人困惑。当然,真正的意义在于Type 是一个描述类型的类,而Customer 是一个描述客户的类。人们对 Customer 是一种类型这一事实感到困惑,尽管 Customer "is-a" Type 不是真的(因为在教学继承中使用了短语 "is-a")。当然,一个相关的混淆是静态(编译时)和动态(运行时)类型之间的区别。该示例暗示了这一点,但我不想明确提及。
    • 确实! (并同意,“将类型作为对象传递”更清晰)。很难解释,你的代码和上面的解释都很棒!
    • @DavidEdey 更清楚,我不知道,因为“作为对象”可能被理解为“作为object”,也就是说,“作为System.Object”不是真的很重要。
    • 嗯,没错!我想这只是一个非常令人困惑的点......也许我们可以说“T 是一种类型,并且可以用作替换代码中的另一种类型”,而“typeof(T), tInstance.getType() 等给出一个对象,它表示类型 T".. 或者甚至可能只是 "而 typeof(T) is 是一个对象"。不知道这是否更清楚哈哈。对不起,这个帖子毫无意义!哈哈
    【解决方案3】:

    似乎“正确”的实现是摆脱 Type 约束:

    public class Service {
        public void Fn<T>(): where T : I, new() {
            ( new T() ).M();
            // ...
        }
    }
    
    public class Main {
        public void main() {
            Service.Fn<C>();
            Service.Fn<D>();
            // ...
        }
    }
    

    如果你真的,真的想要传入一个类型,那么你将不得不使用反射而不是泛型:

    public class Service {
        public void Fn<T>(Type t) {
            I i = Activator.CreateInstance(t) as I;
            if(i != null)
                i.M();
            // ...
        }
    }
    

    问题是你不能设置t 是一个也实现I 的类型的编译时约束。

    【讨论】:

    • 可以从 type 继承,即使它不是真正想要做的。
    • 我将使用您的第一个版本,因为它更清洁/更易于使用
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-07
    • 2018-07-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多