在泛型C#类中链接隐式运算符

9

对于下面这个通用的C#类,我想把T转换为K:

public abstract class ValueType<T,K> : IValueType<T> where K : ValueType<T,K>,new()
{     
    public abstract T Value { get; set; }     

    public static implicit operator ValueType<T,K>(T val) 
    {         
        K k = new K();
        k.Value = val;
        return k;    
    }
}

如果我要实现一个直接操作符implicit operator K(T val),将会导致编译时错误(CS0556)。
我想尝试链接隐式操作符:
public static implicit operator K(ValueType<T,K> vt){
    return (K)val;
}

但是以下示例仍然会报错,无法进行转换:
public class Test : ValueType<int, Test>
{
    public override int Value{ get; set; }
}

Test t = 6; //complains it's unable to be converted
Console.WriteLine(t.Value);

如果可能的话,我真的希望避免显式转换。

这个问题是基于我之前提出的另一个SO问题而延伸的。


1
我认为编译器根本不会对转换运算符应用隐式链接,所以我不明白这里的重点在哪里。或者我有什么遗漏吗? - Lucero
@Lucero,我在想是否有可能链接多个转换。如果不行的话,是否有解决方法? - Iain Sproat
4个回答

18

如果您要实现自己的隐式转换逻辑,规则非常严格,您可能需要详细了解规范的6.4.4和10.10.3部分,特别是像这样特别复杂的部分。

简而言之,您应该知道一些关键规则:

  • 定义转换的类型必须出现在用户定义的转换的“to”或“from”部分中。例如,您不能定义一个类C,将E转换为F; C必须出现在某个地方。
  • 无法使用自己的转换来替换内置的隐式转换;例如,无法在从C到对象进行转换时发生特殊行为。
  • 用户定义的隐式转换将与最多两个内置隐式转换“链接”,但不与任何其他用户定义的转换“链接”。例如,如果您有一个从C到D的用户定义的隐式转换和一个从D到IFoo的内置隐式转换,则会得到一个从C到IFoo的隐式转换。但是,如果D具有到E的用户定义隐式转换,则不会自动获得从C到E的隐式转换。

如果在Func -> MyClass<T>中定义了隐式转换,似乎这应该适用于lamba,但是编译器实际上似乎并没有链接转换。 - NetMage
@NetMage:你能否引用规范中的一句话来证明你的“应该可行”的说法?这个规范领域比较微妙,请仔细阅读。 - Eric Lippert
@NetMage:或者,从编译器开发人员的角度考虑。您在赋值的右侧有一个lambda表达式,并且左侧有一个类型为MyClass<T>的变量。 MyClass<T>可以从任意多个委托类型进行任意多次转换。请描述一下您认为类型推断应该如何从这些信息中推导出lambda表达式的形式参数类型。我认为,如果您这样做,您就会意识到为什么我没有那样做。 - Eric Lippert
@NetMage:正确;匿名函数转换并不是“标准”的。现在,你可能会说,让我们来修复它。我们只需要将其视为任何其他重载解析问题;lambda-to-delegate转换用于确定运算符的第一个适用性,然后是更好的适用性。源表达式需要被视为没有类型,这在特定性规则方面会稍微复杂一些,因为它们是根据具有类型的表达式编写的。 - Eric Lippert
@NetMage:只要愿意花费宝贵的时间和精力来设计功能、修改规格、实现功能、测试实现、编写文档并发布,所有这些都是可行的。但你是第一个建议我认为这项工作可能比团队可以花时间开发的任何其他功能更值得投入努力的人;你希望削减哪个C#功能以适应这个时间表呢? - Eric Lippert
显示剩余2条评论

2

编译器不会自动进行类型转换,因此这种解决问题的方式行不通。

隐式类型转换在类型检查方面非常严格。如果编译器知道类型,您的第一个片段和Test类是可以工作的:

ValueType<int, Test> t = 6;
Console.WriteLine(t.Value);

问题在于,从类型系统的角度来看,您的 ValueType<int, Test> 并不总是一个 Test,因此隐式转换并不适用于此。Eric Lippert 写了一篇关于这种泛型自我引用的博客文章 - 值得一读!请注意,保留HTML标签。

0
据我所知,我认为您无法将转换链接在一起,对此感到抱歉。
我一直在研究如何创建解析器,如果这是可能的话,就必须有一个无限循环来找到从T到K的连接。我不确定C#解析器是否会尝试这样做,但不幸的是,我的钱不在这里!

如果你将类型系统看作是一张类型图,那么很容易通过不重新访问已经在此次尝试中先前访问过的任何类型(节点)来避免在循环上产生无限循环。所以这不是原因... ;) - Lucero
有趣的Lucero,你认为这可以使用访问者模式完成,还是通过在某种映射中跟踪已访问的类型来完成? - Darkzaelus
我不喜欢也不使用访问者模式,因为它违反了基本的开闭原则。话虽如此,最简单的方法是将已访问节点的集合作为参数传递并进行检查,或者采用广度优先搜索方法,将待处理的节点添加到队列中,并在出队时丢弃已经存在于集合中的节点。 - Lucero
我对JavaCC和它内置的访问者支持只有一点经验,而且我也不太喜欢它!我同意你的第二个想法,这样做也更容易丢弃更改,因为使用访问者模式的话,你还得回去修改你设置的bool变量! - Darkzaelus
基本上,这归结于将事物视为对象图而不仅仅是树。树从不具有循环,图通常具有循环,因此在这种情况下,查看处理图的算法是正确的方法。顺便说一句,如果您想在.NET中进行一些解析,您可能需要查看此处 - (哦,还有免责声明,我是该文章中使用的解析器引擎的作者,因此我对这个解析器引擎有一些偏见;))。 - Lucero
我会阅读那个链接,我对解析器很着迷,并且目前正在用Java创建自己的解析器(我必须处理UTF-8文件,而我找到的所有解析器生成器都无法正确处理Unicode组(JavaCC、Antlr、YACC、LEMON))。你花了多长时间创建这个解析器引擎? - Darkzaelus

0

这是我想到的。虽然不是对OP问题的答案,但就我所知,根据C#规则,无论如何都不可能实现。 因此,我的做法是在依赖于抽象类定义的转换算法的具体类中实现隐式操作符。

我的类:

public interface IInjectable<T>
{
    T Value { get; set; }
}

internal abstract class Injectable<T,P> : IInjectable<T>
    where P : Injectable<T,P>, new()
{
    public abstract T Value { get; set; }

    public static implicit operator T(Injectable<T,P> injectable) => injectable.Value;
    
    //public static implicit operator Injectable<T,P>(T value) => new P { Value = value };
    public static P Convert(T value) => new P { Value = value };        
}

internal class InjectableGuid : Injectable<Guid, InjectableGuid>
{
    public override Guid Value { get; set; } = Guid.Empty;

    public override string ToString() => Value.ToString();        

    public static implicit operator InjectableGuid(Guid guid) => Convert(guid);        
}

用法:

Guid id = new InjectableGuid();
Console.WriteLine(id.ToString());

Guid newId = Guid.NewGuid();
Console.WriteLine("Guid.ToString() : "+newId.ToString());
InjectableGuid injected = newId;
Console.WriteLine("InjectableGuid.ToString() : "+injected.ToString());

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