我在尝试创建一个严重依赖于泛型的类的新实例时遇到了以下异常:
new TestServer(8888);
System.TypeLoadException
GenericArguments[0], 'TOutPacket', on
'Library.Net.Relay`4[TInPacket,TOutPacket,TCryptograph,TEndian]'
violates the constraint of type parameter 'TInPacket'.
at System.RuntimeTypeHandle.Instantiate(RuntimeTypeHandle handle, IntPtr* pInst, Int32 numGenericArgs, ObjectHandleOnStack type)
at System.RuntimeTypeHandle.Instantiate(Type[] inst)
at System.RuntimeType.MakeGenericType(Type[] instantiation)
我对这种情况感到困惑。难道泛型约束不是在编译时检查吗?
我的谷歌搜索得出的结论是,这与以下原因有关,或者(有时?)两者都有:
- 类中定义泛型约束(
where
)的顺序; - 使用自引用泛型模式(违反直觉但合法,详见Eric Lippert 的博客文章)。
我不愿意放弃自引用模式。 我绝对需要它来实现特定的目的。
然而,我希望有人能帮我指出问题出现的地方和原因。由于库非常大且具有复杂的泛型模式,因此我认为最好逐步按请求提供代码片段。
如果请求,请再次声明。 但是,我想强调的是,我宁愿知道为什么会发生这样的异常,然后自己在特定代码中修复它,而不是找到一个具体的修复方法。同时,任何分析代码的人给出一个通用的解释,比给出一个具体的修复方法需要更长的时间,以解释为什么泛型类型约束可能在运行时被违反。
实现声明:
class TestServer : Server<TestServer, TestClient, ServerPacket.In, ServerPacket.Out, BlankCryptograph, LittleEndianBitConverter>
class TestClient : AwareClient<TestOperationCode, TestServer, TestClient, ServerPacket.In, ServerPacket.Out, BlankCryptograph, LittleEndianBitConverter>
class ServerPacket
{
public abstract class In : AwarePacket<TestOperationCode, TestServer, TestClient, ServerPacket.In, ServerPacket.Out, BlankCryptograph, LittleEndianBitConverter>.In
public class Out : OperationPacket<TestOperationCode, LittleEndianBitConverter>.Out
}
public enum TestOperationCode : byte
库声明:
public abstract class Server<TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian> : IDisposable
where TServer : Server<TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian>
where TClient : Client<TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian>
where TInPacket : Packet<TEndian>.In
where TOutPacket : Packet<TEndian>.Out
where TCryptograph : Cryptograph, new()
where TEndian : EndianBitConverter, new()
public abstract class Relay<TInPacket, TOutPacket, TCryptograph, TEndian> : IDisposable
where TInPacket : Packet<TEndian>.In
where TOutPacket : Packet<TEndian>.Out
where TCryptograph : Cryptograph, new()
where TEndian : EndianBitConverter, new()
public abstract class Client<TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian> : Relay<TInPacket, TOutPacket, TCryptograph, TEndian>, IDisposable
where TServer : Server<TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian>
where TClient : Client<TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian>
where TInPacket : Packet<TEndian>.In
where TOutPacket : Packet<TEndian>.Out
where TCryptograph : Cryptograph, new()
where TEndian : EndianBitConverter, new()
public abstract class Packet<TEndian> : ByteBuffer<TEndian>, IDisposable
where TEndian : EndianBitConverter, new()
{
public abstract class In : Packet<TEndian>
public abstract class Out : Packet<TEndian>
}
public class OperationPacket<TOperationCode, TEndian>
where TEndian : EndianBitConverter, new()
{
public class In : Packet<TEndian>.In
public class Out : Packet<TEndian>.Out
}
public abstract class AwareClient<TOperationCode, TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian> : Client<TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian>, IDisposable
where TCryptograph : Cryptograph, new()
where TInPacket : AwarePacket<TOperationCode, TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian>.In
where TOutPacket : Packet<TEndian>.Out
where TServer : Server<TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian>
where TClient : AwareClient<TOperationCode, TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian>
where TEndian : EndianBitConverter, new()
public class AwarePacket<TOperationCode, TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian>
where TCryptograph : Cryptograph, new()
where TInPacket : AwarePacket<TOperationCode, TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian>.In
where TOutPacket : Packet<TEndian>.Out
where TServer : Server<TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian>
where TClient : AwareClient<TOperationCode, TServer, TClient, TInPacket, TOutPacket, TCryptograph, TEndian>
where TEndian : EndianBitConverter, new()
{
public abstract class In : OperationPacket<TOperationCode, TEndian>.In
}
正如评论中所指出的那样,对我来说获得这个问题的帮助的最简单方法是将代码最小化为一个小而可重复的示例,在其中错误仍然存在。然而,这对我来说既困难又漫长,并且很有可能会使错误变成Heisenbug,因为它是由于复杂性而发生的。
我试图将其隔离到以下内容中,但当我这样做时,我并没有遇到这个错误:
// Equivalent of library
class A<TA, TB, TI, TO> // Client
where TA : A<TA, TB, TI, TO>
where TB : B<TA, TB, TI, TO>
where TI : I
where TO : O
{ }
class B<TA, TB, TI, TO> // Server
where TA : A<TA, TB, TI, TO>
where TB : B<TA, TB, TI, TO>
where TI : I
where TO : O
{ }
class I { } // Input packet
class O { } // Output packet
// Equivalent of Aware
class Ii<TA, TB, TI, TO> : I { } // Aware input packet
class Ai<TA, TB, TI, TO> : A<TA, TB, TI, TO> // Aware capable client
where TA : Ai<TA, TB, TI, TO>
where TB : B<TA, TB, TI, TO>
where TI : Ii<TA, TB, TI, TO>
where TO : O
{ }
// Equivalent of implementation
class XI : Ii<XA, XB, XI, XO> { }
class XO : O { }
class XA : Ai<XA, XB, XI, XO> { }
class XB : B<XA, XB, XI, XO> { }
class Program
{
static void Main(string[] args)
{
new XB(); // Works, so bad isolation
}
}
血腥细节
- 分析异常告诉我们,在
Relay<TInPacket, TOutPacket, TCryptograph, Tendian>
上,TOutPacket
违反了TInPacket
。 - 我们拥有的
Relay
实例是TestClient
,它实现了AwareClient
,AwareClient
又实现了Client
,Client
又实现了Relay
。AwareClient
与AwarePacket
配合使用,这样两端就会知道哪种类型的客户端接收哪种类型的数据包。
- 因此,我们知道
TestClient
中的TOutPacket
违反了TestClient
中的TInPacket
。 - 实现
TOutPacket
的类是ServerPacket.Out
,它是OperationPacket
的一个派生类。就泛型而言,这种类型相对简单,它只提供枚举类型和字节序类型,没有与其他类的交叉引用。结论:问题很可能不在这个声明本身。 - 实现
TInPacket
的类是ServerPacket.In
,它是AwarePacket
的一个派生类。这种类型比TOutPacket
复杂得多,因为它交叉引用泛型以便于“感知”(AwarePacket
)接收它的客户端。问题可能就出在这个泛型混乱的地方。
那么,许多假设可以融合在一起。此时,我所读到的代码与编译器接受的代码是正确的,但很明显有问题。
你能帮我找出为什么我的代码会在运行时出现泛型约束违规的问题吗?
TCryptograph
和TEndian
类型参数让我感到有些奇怪。我认为这些应该是Cryptograph
类型和EndianBitConverter
类型的普通属性,你可以将其分配给派生类的实例。 - CodesInChaosServer
类的声明。 - CodesInChaos