【问题标题】:C# method generic return type castingC# 方法泛型返回类型转换
【发布时间】:2018-11-12 19:56:54
【问题描述】:

我正在尝试在具有泛型返回类型的接口中创建一个方法,但我未能将泛型转换为特定类型/类。但是,如果我将泛型放在接口上而不是方法上,我就可以进行强制转换。

换句话说,为什么会这样

public class Rain {
    public string propA {get;set;}
}
public interface IFoo<T> {
    T foo();
}

public class Bar : IFoo<Rain> {
    Rain foo() { 
        //...
        return new Rain();
    }
}

public bar = new Bar();
Rain rain = bar.foo();

但这不可能吗?

public class Rain {
    public string propA {get;set;}
}
public interface IFoo {
    T foo<T>();
}

public class Bar : IFoo {
    T foo<T>() { 
        //...
        return new Rain();
    }
}

public bar = new Bar();
Rain rain = bar.foo<Rain>();

还有其他方法吗(不使用Convert.ChangeType())?

【问题讨论】:

  • 在你的第二个版本中,你声明 Foo 返回 T 类型的东西——类型可以是任何东西。但是在调用 Foo 并传递类型 T 时,它必须返回 T 类型的东西,但你总是返回 Rain。本质上就是说你要返回一个字符串但返回一个int,当然它不会编译
  • 为了帮助加深您的误解,想象一下如果您的第二个代码示例是 public bar = new Bar(); Bobby rain = bar.foo&lt;Bobby&gt;();,您期望编译器会做什么。现在,编译器要做的唯一合理的事情就是说“不,我不能那样做”。因此,它必须始终这样说 - 无论您是否将 Bobby 替换为 Rain

标签: c# generics


【解决方案1】:

第二个代码 sn -p 无法编译,因为Rain 不是T

在类中提供类型参数时,没有问题,因为该方法只能返回类声明中已经提供的类型。换句话说 - T foo() 变为 Rain foo

但是,当向方法提供类型参数时,该方法有义务返回提供给它的任何类型 - 因此您不能返回除 T 之外的任何内容。也就是说,编译器不能强制调用方法只使用foo&lt;Rain&gt;(),但可以强制foo方法返回T

【讨论】:

    【解决方案2】:

    区别在于:

    // On this line you specify that the interface's generic type paramter 'T' is of type 'Rain',
    // so the method implements the interface and returns a 'Rain'
    public class Bar : IFoo<Rain> {
        Rain foo() { // <= implements IFoo<Rain>.foo, where T = Rain so foo returns 'Rain'
            return new Rain();
    
    // In this version, the generic type parameter is declared on the method. It could be any type when the method is called, yet you always return a 'Rain'
    public class Bar : IFoo {
        T foo<T>() { // <= implements IFoo.foo<T> but the type of T is not specified yet
            return new Rain();
    

    对此的“解决方案”取决于您的意图。

    • 为什么您想要接口上的泛型参数?
    • 另外,如果您总是返回 Rain,为什么还要在 foo 方法上使用通用参数?

    当然,在任何情况下,您都可以像这样投射它:

    T Foo<T>()
    {
        object result;
        result = new Rain();
        return (T)result; // note, this will throw at runtime if 'result' cannot be cast to 'T'
    }
    
    // call like this:
    Bar.Foo<Rain>();
    

    但我认为您的第一种方法 IFoo&lt;T&gt; 非常合理,那么为什么不使用它呢?

    更新

    根据您的评论:您还可以定义多个泛型参数:

    public interface IFoo<T1, T2>
    {
        T1 foo();
        T2 foo2();
    }
    
    // implementation:
    public class Bar : IFoo<Rain, Other>
    {
        Rain foo() { /* ... */ }
        Other foo2() { /* ... */ }
    }
    

    【讨论】:

    • 由于Rain 是一个类(因此不是值类型),因此不涉及装箱。 Boxing 是将值类型转换为引用类型。此外,如果Rain 是值类型,这将引发InvalidCastException - 您只能取消装箱到与装箱完全相同的类型,即使从装箱类型到您想要的类型的隐式转换也是如此拆箱。除此之外,这是一个很好的答案。
    • 所以事实上,你可以做return new Rain() as T(如果Rain不能转换成T,那将返回default(T)
    • @ZoharPeled 有效点。 T 可能虽然是一个值类型。另外,在这种情况下我不会使用as,我更喜欢代码抛出意外行为(比如当你搞砸了这个设计并且无法进行强制转换时)而不是默默地返回null。
    • @marsze 我不想要接口上的泛型参数,因为我有多个具有不同 T 类型的泛型方法。另外我想让该接口的各种实现来决定返回类型是什么
    • @DevelopeCruz “另外我想让该接口的各种实现来决定返回类型是什么” - 在这种情况下,接口非常有限,分别没有任何用处。如果您的接口不限制实现可以返回的内容,只需摆脱接口即可。或者更好的是,重新考虑您的设计。
    【解决方案3】:

    如果您需要您的接口支持任意一组类,并且使用哪些类由调用者确定,那么这样的方法是最通用的解决方案。

    public class Bar : IFoo {
        T foo<T>() { 
            if (typeof(T)==typeof(Rain))
               return new Rain() as T;
            if (typeof(T)==typeof(Snow))
               return new Snow() as T;
            Throw new ArgumentException("Not implemented for " + typeof(T).Name);
        }
    }
    

    如果你所有的 T 都有一个共同的界面,那是你感兴趣的,你可以这样做;

    public class Snow : IWeather {...}
    public class Rain: IWeather {...}
    public class Bar : IFoo {
        IWeather  foo<T>() T : IWeather { 
            if (typeof(T)==typeof(Rain))
               return new Rain();
            if (typeof(T)==typeof(Snow))
               return new Snow();
            Throw new ArgumentException("Not implemented for " + typeof(T).Name);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2016-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多