I'm getting the following exception while trying to create a new instance of a class that heavily relies on generics:
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)
I'm puzzled as to why this happens. Aren't generic constraints checked at compile time?
My googling brought me to the conclusion that this has something to do with either of these causes, or (sometimes?) both:
where
) are defined in the classes;One thing I am not ready to sacrifice is the self-referencing pattern. I absolutely need it for a specific purpose.
However, I'd like some help to point out where and why this problem occurs. As the library is massive and makes huge generic patterns, I think it would be best to progressively give code bits on request.
Upon request, declarations again. But I'd like to stress the fact that I would rather know generally why an exception like this can occur and then proceed to fix it myself in my specific code rather than find a specific fix, for posterity. Also, it will be much longer for anyone analyzing the code to answer than to give a general explanation as to why generic type constraints can be violated at runtime.
Implementation declarations:
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
Library declarations:
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
}
As noted in the comments, the simplest way to get help on this question for me would be to minimize the code to a small and reproducible example in which the bug is still present. However, this is both hard and long for me, and has the high chances of making the bug a heisenbug, as it occurs from complexity.
I tried to isolate it to the following, but I don't get the bug when I do:
// 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
}
}
Gory Details
TOutPacket
violates TInPacket
on Relay<TInPacket, TOutPacket, TCryptograph, Tendian>
. Relay
we have is TestClient
, which implements AwareClient
, which implements Client
, which implements Relay
.
AwareClient
is used in conjunction with AwarePacket
so that both ends are aware of which type of client receives which type of packets.TOutPacket
in TestClient
violates TInPacket
in TestClient
.TOutPacket
is ServerPacket.Out
, which is a derivative of OperationPacket
. This type is relatively simple in terms of generics, as it only provides an enum type and an endian type, making no cross-reference to other classes. Conclusion: The problem is not (most likely) not in this declaration by itself.TInPacket
is ServerPacket.In
, which is a derivative of AwarePacket
. This type is much more complex than TOutPacket
, since it cross-references generics to be aware (AwarePacket
) of the client that received it. It is probably in this generic mess that the problem occurs.Then, many hypotheses can fuse. At this point, what I read is correct and accepted by the compiler, but there is evidently something wrong there.
Can you help me find out why I'm getting a generic constraint violation at runtime with my code?
It had nothing to do with all the generic constructs. Believe it or not, my design was stable and functional.
The actual cause was the only thing I didn't suspect: the int port
parameter passed to new TestServer(int port)
.
This int
was actually obtained through a dynamic expression which is irrelevant. Let's say it was
dynamic GetPort() { return 8888; }
new TestServer(GetPort()); // Crash
new TestServer((int)GetPort()); // Works
Apologies to CodeInChaos for saying I used no reflection, I guess that was only half-true.
Now, the bounty is started and the bug is still there (I want to use my dynamic method). So, could anyone a) explain why this happens (after all, the type is valid) and b) propose a way to fix it? Bounty and accepted answer will go to that person.
If you want to experiment, I got this code to reproduce and crash: http://pastie.org/2277415
If you want the actual executable that crashes, along with the solution and project: http://localhostr.com/file/zKKGU74/CrashPlz.7z