【问题标题】:Type inference when inheriting generic type and constraint继承泛型类型和约束时的类型推断
【发布时间】:2013-07-30 17:44:24
【问题描述】:

我正在构建一个接口以允许 NHibernate 从自定义类型转换为字符串,反之亦然。

public interface IStringToTypeConverter<T>
{
    T FromString(string value);
    string ToString(T value);
}

T 类型可以是任何类型。对于这个例子,我将使用一个枚举,我知道 NHibernate 有转换枚举的工具,但这是最简单的问题示例。

public enum TransactionStatus { Failed, Pending, Succeeded }

public class TransactionStatusConverter : IStringToTypeConverter<TransactionStatus>
{
    public TransactionStatus FromString(string value)
    {
        return (TransactionStatus)Enum.Parse(typeof (TransactionStatus), value, true);
    }

    public string ToString(TransactionStatus value)
    {
        return value.ToString();
    }
}

到目前为止一切都很好,但是当我尝试将 TransactionStatusConverter 指定为具有泛型类型和约束 IStringToTypeConverter&lt;T&gt; 的类时,问题就出现了。

public sealed class CustomStringType<TConverter, TType> : IUserType where TConverter : IStringToTypeConverter<TType>, new()
{
    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        var value = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
        var converter = new TConverter();
        return converter.FromString(value);
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        var converter = new TConverter();
        ((IDataParameter)cmd.Parameters[index]).Value
            = converter.ToString((TType)value);
    }
}

这个类在NHibernate中的用法:

Property(x => x.Status, map =>
{
    map.Column("TransactionStatusID");
    map.Type<CustomStringType<TransactionStatusConverter, TransactionStatus>>();
}); 

其中Type&lt;&gt;()函数的类型约束为IUserType

我的问题是,我觉得在map.Type&lt;CustomStringType&lt;TransactionStatusConverter, TransactionStatus&gt;&gt;();TransactionStatus 是多余的,因为它应该通过转换器的类型来推断。

我将如何写 CustomStringType 这样我的映射代码读取为 map.Type&lt;CustomStringType&lt;TransactionStatusConverter&gt;&gt;();

【问题讨论】:

  • 我可能错了,但new() 上的TConverter : IStringToTypeConverter&lt;TType&gt;, new() 约束不会强制TType 成为classstruct(在你的情况下它是@987654339 @)?
  • @AndrewCoonce Nope T : new() 仍然适用于enums,并返回default(T)

标签: c# .net generics interface


【解决方案1】:

如果您可以省略类型参数,那么没有什么可以阻止您拥有一个实现IStringToTypeConverter&lt;Foo&gt;IStringToTypeConverter&lt;Bar&gt; 的类。如果该类两者都有,它可以安全地推断出应该将哪种类型用作CustomStringType 的第二个类型参数?请记住:您只能做出编译器可以做出的逻辑假设,不能作弊并使用您的领域知识来解决问题!

另一方面,如果你翻转它并只提供TransactionStatus 类型,暗中希望反射能以某种方式解决你的问题,那么两个不同的类可能都实现IStringToTypeConverter&lt;TransactionStatus&gt;。你仍然有一个问题,你不知道哪一个对你的问题是正确的。同样,如果没有领域知识,这个问题对于编译器来说是难以解决的。

【讨论】:

  • 我理解你发现的一个类从IStringToTypeConverter 继承了两次的问题。编译器不知道仅通过类型推断哪一个。不过,我将等待一段时间,看看是否有一个优雅的解决方案可以解决我的问题。
  • 我很想弄错,它感觉确实应该有一个好的解决方案......但它也感觉像一个好的解决方案无法解决。
【解决方案2】:

C# 不支持泛型类型参数的部分推断。

一种方法是使用 using alias 指令:

using CustomStringTypeTransactionStatusConverter = CustomStringType<TransactionStatusConverter, TransactionStatus>;

那么你可以这样做:

map.Type<CustomStringTypeTransactionStatusConverter>();

请注意,上述方法比定义子类更好,因为这样基类和子类是不同的类型(如果 您认为它们在语义上是相同的类型并且想要在方法参数等中使用它们):

// This is bad because it defines a new type
public class SubClass : CustomStringType<TransactionStatusConverter, TransactionStatus>
{
}

但是,在您的特定情况下,您可以进行一些简单的重构(请注意,在您的代码中,您只使用 TType 来转换对象,并且这种转换可以在 TransactionStatusConverter 本身中完成)

先引入一个额外的接口:

public interface IStringToTypeConverter<T> : IStringToTypeConverterUntyped
{
    T FromString(string value);
    string ToString(T value);
}

public interface IStringToTypeConverterUntyped
{
    object FromStringUntyped(string value);
    string ToString(object value);
}

实现成员:

public class TransactionStatusConverter : IStringToTypeConverter<TransactionStatus>
{
    public TransactionStatus FromString(string value)
    {
        return (TransactionStatus)Enum.Parse(typeof(TransactionStatus), value, true);
    }

    public object FromStringUntyped(string value)
    {
        return FromString(value);
    }

    public string ToString(TransactionStatus value)
    {
        return value.ToString();
    }

    public string ToString(object value)
    {
        return ToString((TransactionStatus)value);
    }
}

并修改CustomStringType:

public sealed class CustomStringType<TConverter>
    where TConverter : IStringToTypeConverterUntyped, new()
{
}

现在你可以像这样使用它了:

map.Type<CustomStringType<TransactionStatusConverter>>();

附加说明:如果您有许多转换器并且不想在每个转换器中实现 IStringToTypeConverterUntyped(例如在 TransactionStatusConverter 中),那么您可以让它们都继承自实现 IStringToTypeConverterUntyped 中声明的成员并声明的基类 ConverterBase在 IStringToTypeConverterT> 中声明的成员的抽象成员。

【讨论】:

  • 感谢您的建议,我选择了与您的第二个示例类似的内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-06-23
  • 2015-07-04
  • 2020-12-01
  • 2022-11-23
  • 2021-08-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多