【发布时间】:2014-06-17 00:46:57
【问题描述】:
我在尝试创建严重依赖泛型的类的新实例时遇到以下异常:
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's blog post)
我还没有准备好牺牲的一件事是自我引用模式。我绝对需要它用于特定目的。
但是,我需要一些帮助来指出此问题发生的位置和原因。由于库非常庞大,并且生成了大量的通用模式,我认为最好根据请求逐步提供代码位。
应要求,再次声明。但我想强调一个事实,即我宁愿一般地知道为什么会发生这样的异常,然后继续在我的特定代码中自己修复它,而不是为后代找到特定的修复程序。此外,任何分析代码的人都比给出一般解释为什么在运行时会违反泛型类型约束要长得多。
实现声明:
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
}
正如 cmets 中所述,对我来说,在这个问题上获得帮助的最简单方法是将代码最小化为仍然存在错误的小型且可重现的示例。但是,这对我来说既困难又漫长,并且很有可能使该错误成为海森错误,因为它是由复杂性引起的。
我尝试将其隔离为以下内容,但这样做时我没有得到错误:
// 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
}
}
血腥细节
- 分析异常告诉我们
TOutPacket在Relay<TInPacket, TOutPacket, TCryptograph, Tendian>上违反了TInPacket。 - 我们拥有的
Relay的实例是TestClient,它实现了AwareClient,它实现了Client,它实现了Relay。-
AwareClient与AwarePacket配合使用,让两端都知道哪种类型的客户端接收哪种类型的数据包。
-
- 因此,我们知道
TestClient中的TOutPacket违反了TestClient中的TInPacket。 - 实现
TOutPacket的类是ServerPacket.Out,它是OperationPacket的派生类。这种类型在泛型方面相对简单,因为它只提供了一个枚举类型和一个字节序类型,没有对其他类的交叉引用。结论:问题本身并不(很可能)不在此声明中。 - 实现
TInPacket的类是ServerPacket.In,它是AwarePacket的派生类。这种类型比TOutPacket复杂得多,因为它交叉引用泛型以意识到 (AwarePacket) 接收它的客户端。问题可能就是在这种普遍的混乱中发生的。
然后,许多假设可以融合。在这一点上,我读到的内容是正确的并被编译器接受,但显然那里有问题。
您能帮我找出为什么我的代码在运行时会违反一般约束吗?
【问题讨论】:
-
您是否使用反射来专门化泛型类型?或者也许使用一个库?
-
附带说明:我很确定您有点过度使用泛型。特别是
TCryptograph和TEndian类型参数让我觉得很奇怪。我认为这些应该是Cryptograph和EndianBitConverter类型的普通属性,您可以为其分配派生类的实例。 -
您的程序集是否可验证?你的代码去哪儿了?您刚刚发布了
Server课程的声明。 -
我宁愿远离设计问题。例如,需要
TEndian,但我可以使用 TCryptograph。只有我发现这样更方便。 -
@Lazlo:你能发布堆栈跟踪吗?
标签: c# .net generics runtime type-constraints