C#和转换泛型参数

4

我来自C++模板编程,有时会对泛型产生困惑。由于没有方法特化,我尝试使用强制转换。以下是我的代码:

public interface INonGen
{
    void Get<T>(ref T value);
}

public interface IGen<U> : INonGen
{

}

public class Gen<U> : IGen<U>
{
    private U u;
    public void Get<T>(ref T value)
    {
        if (value is U)
        {
            value = (T) u;
        }         
        else
            throw new Exception();
    }
}

这段代码无法编译。

有没有办法进行强制转换?

我想要这样做的原因是:使用C++模板,我可以为支持的类型创建专门的实现,以及抛出异常的非专用版本。

基本思路是:一个非泛型接口,其中包含一个泛型方法。使用正确类型尝试获取值应该是有效的,尝试使用错误类型可能会抛出异常。

我们应该保持类型安全,所以我需要返回正确类型的实例/值。任何超过对象的捷径都是不可接受的,约束非泛型接口中的类型也是不可接受的。

泛型实现是为了避免重复。我想要支持多种不同的类型(但仅支持一小组类型),但我希望在实例化类(并定义T的含义)时决定这一点;我仍然希望非泛型接口允许使用任何T来访问;也就是说,我不希望在接口中显式列出类型集合。


http://msdn.microsoft.com/en-us/library/d5x73970.aspx - Jodrell
7
你想要实现什么目标?也许有比你现在的方法更好的方式。 - dtb
1
在模板化编程中,比如C++,似乎可以节省工作量,但实际上会阻碍静态分析,并将你的工作推迟到未来,到那时你将面临更少的时间和更大的压力。利用类型安全帮助你尽早发现问题。 - Jodrell
@Jodrell,那并没有帮助。通过特化,我可以轻松地创建一些Get<double>,Get<ISomething>实现并抛出其他异常。我想在C#中有类似的功能。 - Wilbert
@Wilbert,当T未被处理时,您如何处理异常? - Jodrell
@Jodrell 这里不是问题的一部分,但好吧:Dict<SomeKey, INonGen>; 我查找键并希望从结果存储库中获取值。当一个键与错误类型匹配时,异常被抛出;所以这没问题。 - Wilbert
4个回答

3

将一个对象转换为另一个对象时,如果编译器无法找到一种转换方式,它会报错。由于这两个类型参数没有限制,唯一的选择是使用as运算符。当类型转换失败时,as不会抛出InvalidCastException,而是返回null。要使用as,您还需要将泛型类型限制为类。

public class Gen<U> : IGen<U>
{
    private U u;
    public void Get<T>(ref T value)
            where T : class
    {
        if (value is U)
        {
            value = u as T;
        }         
        else
            throw new Exception();
    }
}

如果您不想添加约束条件,可以将其转换为 Object

value = (T)(object)u;

但是你的代码存在逻辑错误。如果value is U,那么什么保证u is T呢?例如:

 var gen = new Gen<Base>();
 gen.Set(new DerivedA()); // sets u;
 var b = new DerivedB();
 gen.Get(ref b);

在这种情况下,value is Base,但不是u is DerivedB。转换将在运行时失败。 更新 阅读了一些评论后,我会这样设计:
public interface INonGen
{
    object Value { get; }
}

public interface IGen<U> : INonGen
{
}

public class Gen<U> : IGen<U>
{
    private U u;
    public object Value
    {
       get { return u; }
    }
}

当从字典中获取项目时:

double value = (double)dictionary[key].Value;

如果没有运行时转换,将抛出“InvalidCastException”异常。简单明了,不是吗?

但是你会失去类型安全性。 - Wilbert
嗯,确实。直觉上,我不喜欢这个答案,但它解决了我的问题。 - Wilbert
@Wilbert:你在我的回答评论中描述的是运行时类型检查。这就是为什么你不喜欢它的原因。你能解释一下为什么你希望该方法可以使用任何T进行调用吗? - Mike Goodwin
@MikeGoodwin 这是查找的内部部分。我有一个唯一键的集合(在整个应用程序中),它们映射到值或实例;我从 Dict<MyKey, INonGen> 中查找非泛型 repo,然后想获取值或实例。我希望单个字典用于所有键到 repo 的查找,并且如果不匹配类型,则抛出异常,因为这样调用者的逻辑就有错误了。 - Wilbert
@EliArbel 刚刚看到你的更新答案了:不,因为在这种情况下,调用者不需要指定类型。我希望调用者调用 Get<调用者期望的类型>,而不是使用 object get。 - Wilbert
显示剩余4条评论

1

我不确定在这个上下文中INonGen的目的,特别是它有一个泛型方法。如果你去掉那个,你就可以做到这一点。这是可编译的 - 我检查过了; o)

public interface IGen<T>
{
    void Get(ref T value);
}

public class Gen<T, U> : IGen<T> where U : T
{
    private U u;

    public void Get(ref T value) 
    {
        if (value is U)
        {
            value = (T)u;
        }
        else
            throw new Exception();
    }
}

重点是,你不能仅在接口方法上有通用类型参数,因为这会防止你在实现类中指定约束条件。它必须在接口定义本身上。并且实现类必须明确知道两个通用类型参数U和T,以便编译器可以验证转换操作。您可以使用as,但这不是类型安全的,所以您必须处理结果为null的情况,而不是依赖编译器来处理。不建议这样做。如果在您的实际示例中,INonGen具有其他非通用方法或通用方法,在实现方法之外,实现类不需要了解方法的通用类型参数,则可以毫不费力地恢复它。
public interface INonGen
{
    void NonGenericMethod();
    void GenericMethod<V>(V parameter);
}

public interface IGen<T> : INonGen
{
    void Get(ref T value);
}

public class Gen<T, U> : IGen<T> where U : T
{
    private U u;

    public void Get(ref T value) 
    {
        if (value is U)
        {
            value = (T)u;
        }
        else
            throw new Exception();
    }

    public void NonGenericMethod()
    {
    }

    public void GenericMethod<V>(V parameter)
    {
    }
}

在第一个例子中,您永远不会遇到异常,因为代码无法编译。 - Jodrell
我希望从非泛型接口中获取Get<T>,并且可以使用任何T进行调用;但是只有当T为U时才应该成功调用。 - Wilbert

0

你需要使用一个叫做约束条件的概念...

public class Gen<U> : IGen<U> where U : T
{
    private U u;
    public void Get<T>(ref T value)
    {
        if (value is U)
        {
            value = (T) u;
        }         
        else
            throw new Exception();
    }
}

这告诉你,U 必须是或派生自提供给 T 的参数。

仍然无法构建。出现错误 找不到类型或命名空间名称“T”(是否缺少 using 指令或程序集引用?) - Szymon
1
虽然这个想法在这里(使用约束)存在,但它无法编译:T 泛型类型需要在主类定义中知道。 - Larry
通用类必须接受任何T,但只有在T匹配U时才进行转换/返回值。 - Wilbert

0
首先,也不要使用 ref。这样你的问题就不存在了。
public interface IThing
{
    object Value { get; }
}

public interface IThing<T> : IThing
{
    T Value { get; }
}

public class Thing<T> : IThing<T>
{
    private T t;

    public object Value
    {
        get
        {
            return this.Get();
        }
    }

    public T Value<T>()
    {
        get
        {
            return this.t;
        }
    }
}

如果你真的想接受一些通用类型,那就要限制它的正确类型

public interface ICrazyThing<T>
{
    void Get<T>(ref T crazy);
}

public class CrazyThing<U, T> : IThing<T> where T : U
{
    private U u;

    public void Get<T>(ref T crazy)
    {
       crazy = this.u;   
    }
}

即使在疯狂的世界中,outref更好,因为传入的值是一个毫无意义的实例化,与结果无关。

out在内部与ref相同(在C++/Cli中也是如此),但在显式初始化之前,我可以对其进行更多类型确定。 - Wilbert
@Wilbert,抱歉,我不明白你所说的“更多类型确定”的意思。 - Jodrell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接