Я получаю следующее исключение, пытаясь создать новый экземпляр класса, который сильно зависит от дженериков:
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)
Я озадачен, почему это происходит. Не проверяются ли общие ограничения во время компиляции?
Мой googling привел меня к выводу, что это имеет какое-то отношение к любой из этих причин или (иногда?):
- Порядок, в котором общие классы (
where
) определены в классах; - Использование шаблона с саморегуляцией (conter-intuitive, но очень легально, см. сообщение в блоге 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
}
Как отмечалось в комментариях, самый простой способ получить помощь по этому вопросу для меня будет заключаться в том, чтобы свести код к небольшому и воспроизводимому примеру, в котором ошибка все еще присутствует. Однако для меня это и сложно, и долго, и имеет большие шансы сделать ошибку гейзенбугом, как это происходит из-за сложности.
Я попытался выделить его следующим образом, но я не получаю ошибку, когда я это делаю:
// 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
- Анализ исключения указывает нам, что
TOutPacket
нарушаетTInPacket
наRelay<TInPacket, TOutPacket, TCryptograph, Tendian>
. - У экземпляра
Relay
естьTestClient
, который реализуетAwareClient
, который реализуетClient
, который реализуетRelay
.-
AwareClient
используется в сочетании сAwarePacket
, чтобы оба конца знали, какой тип клиента получает тот тип пакетов.
-
- Поэтому мы знаем, что
TOutPacket
вTestClient
нарушаетTInPacket
вTestClient
. - Класс, реализующий
TOutPacket
,ServerPacket.Out
, который является производной отOperationPacket
. Этот тип относительно прост с точки зрения дженериков, поскольку он предоставляет только тип перечисления и тип endian, не делая перекрестной ссылки на другие классы. Вывод: проблема не (скорее всего) не в этом объявлении сама по себе. - Класс, реализующий
TInPacket
,ServerPacket.In
, который является производной отAwarePacket
. Этот тип намного сложнее, чемTOutPacket
, так как он перекрестно ссылается на генерики, чтобы знать (AwarePacket
) клиента, который его получил. Вероятно, в этой общей проблеме возникает проблема.
Тогда многие гипотезы могут слиться. На этом этапе, что я читаю, правильно и принято компилятором, но, очевидно, что-то не так.
Можете ли вы помочь мне выяснить, почему я получаю нарушение общих ограничений во время выполнения с моим кодом?