为什么我在运行时遇到泛型约束违规?

时间:2011-07-24 20:23:07

标签: c# .net generics runtime type-constraints

我在尝试创建一个严重依赖于泛型的类的新实例时遇到以下异常:

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
}

正如评论中所指出的,为我提供这个问题帮助的最简单方法是将代码最小化到一个小的,可重现的例子,其中仍然存在bug。然而,这对我来说既困难又漫长,并且很有可能使这个bug成为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
    }
}

血腥细节

  1. 分析该异常会告诉我们TOutPacket违反了TInPacket上的Relay<TInPacket, TOutPacket, TCryptograph, Tendian>
  2. 我们Relay的实例是TestClient,它实现AwareClient,实现Client,实现Relay
    • AwareClientAwarePacket一起使用,以便两端都知道哪种类型的客户端接收哪种类型的数据包。
  3. 因此,我们知道TOutPacket中的TestClient违反了TInPacket中的TestClient
  4. 实施TOutPacket的班级是ServerPacket.Out,它是OperationPacket的派生词。这种类型在泛型方面相对简单,因为它只提供枚举类型和字节序类型,不会对其他类进行交叉引用。结论:问题本身并非(很可能)不在本声明中。
  5. 实施TInPacket的班级是ServerPacket.In,它是AwarePacket的派生词。此类型比TOutPacket复杂得多,因为它交叉引用泛型,以便感知AwarePacket)接收它的客户端。问题可能出在这个通用的混乱中。
  6. 然后,许多假设可以融合。在这一点上,我读到的内容是正确的,并被编译器接受,但显然有一些错误。

    你能帮助我找出为什么我在运行时使用我的代码获得通用约束违规吗?

4 个答案:

答案 0 :(得分:14)

答案 1 :(得分:8)

它与所有通用结构无关。信不信由你,我的设计稳定而实用。

实际原因是我唯一没有怀疑的:传递给int port的{​​{1}}参数。

new TestServer(int port)实际上是通过一个无关的动态表达式获得的。我们说这是

int

向CodeInChaos道歉,因为我没有使用任何反思,我猜这只是半真的。

现在,赏金开始了,bug仍然存在(我想使用我的动态方法)。那么,任何人都可以a)解释为什么会发生这种情况(毕竟,类型是有效的)和b)提出一种解决方法吗?赏金和接受的答案将归给那个人。

如果你想进行实验,我会让这段代码重现并崩溃:http://pastie.org/2277415

如果您希望崩溃的实际可执行文件以及解决方案和项目:http://localhostr.com/file/zKKGU74/CrashPlz.7z

答案 2 :(得分:3)

我的猜测是,某些旧的编译代码在某处徘徊......特别是如果问题突然消失

  • 你最近有什么类型的论点?
  • 您是否在构建时增加程序集版本? (可能会导致问题 因为类型的完全限定名称更改)
  • 这个异常发生的情况是什么,是客户端吗? 使用二进制文件的不同副本调用服务器?

如果这些问题中的任何一个都是真的,我会删除我能找到的每个二进制文件并从头开始重建所有内容:)

-edit -

此外,请确保您不会直接引用二进制文件,除非您真的必须这样做。您应该始终使用项目引用来确保所有内容都能正确重建。

-edit2 -

好的,这太奇怪了..我在我的游乐场解决方案中粘贴了你的代码,得到了例外。但现在我尝试了你的编译版本,它的工作原理!

我用旧版本对代码进行了区分,完全相同......

我对projfiles进行了区分,并不完全相同,但我复制了所有细节,以便它们在哪里,仍然你的项目工作,我没有!

所以我检查了解决方案文件..从项目guids没有不同的appart ..,仍然是相同的情况..

所以我删除了我唯一能想到的其他东西,我操场解决方案的 .suo文件 ..他们两个都工作了..

suo文件似乎是二进制的,所以我不确定那里确切的设置。我确实知道在安装.net / vs2010 sp1之前我有suo文件,也许那里有一些旧东西,谁知道呢。我会尝试更多地调查。

-edit4 -

嗯,我不知道发生了什么......现在我不能让代码再次崩溃。即使复制旧的.suo文件也不起作用..

答案 3 :(得分:2)

如果你确实没有使用反射,这似乎表明C#编译器或运行时中存在错误。通常这会导致无法验证的代码。

您似乎创建了一个运行时认为非法的构造,但C#编译器并不认为是非法的。由于您省略了基本类型声明,因此很难说哪个错误。