3

I have a non-generic class with a generic method. Generic type on this method defines output type and can't be inferred from usage so I have to explicitly provide generic type. Sometimes this type is passed on from generic caller's method type parameter, but in one instance I have to explicitly provide it myself.

The problem is when I call my explicitly provided generic type method call it seems that it doesn't get executed and returns a completely irrelevant type. I can't debug into this call and get invalid results. But it doesn't break execution which is especially strange. When the same method is called from elsewhere where generic type is passed on from caller's generic method type, everything seems to work as per definition.

I'm completely lost what's going on.

My method definition in the interface (and is implemented later in the class):

TRecord Update<TRecord>(int recordId, int? categoryId, string categoryName, string title)
    where TRecord : Record;

My Record class is non-abstract and and there's only one type that inherits from it:

public class Record : ProtectedEntity
{
    ...
}

public class RelatedRecord<T> : Record
{
    public IList<T> Related { get; private set; }
    ...
}

I'm calling my method doing this:

var record = myRepo.Update<Record>(...);

When execution gets to this line I hit F11 to debug into it, but execution just jumps to the next sentence. When I check my record variable it's not of type Record but rather System.String, having the value of parameter categoryName. This means that something does get executed but it's definitely not the body of my generic method.

The strange thing is that everywhere else the same call works as expected.

How can this be explained and what am I doing wrong?

Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
  • 3
    Looks curious. Can you extract some of your code to MCVE (minimal complete verifiable example)? – Andrey Korneyev Dec 30 '14 at 12:11
  • at the compile time what's the type of record ? what happens when you try to access a member of `Record` or `string` using `record` at runtime ? – Selman Genç Dec 30 '14 at 12:12
  • 2
    does `record.GetType().FullName` return `System.String` ? – Marc Gravell Dec 30 '14 at 12:12
  • @MarcGravell: Yes it does return `System.String`. :S – Robert Koritnik Dec 30 '14 at 12:18
  • @Robert k; can you also check `typeof(Record).FullName`? what is that? – Marc Gravell Dec 30 '14 at 12:19
  • @Selman22: At compile time when I hover `var` beside the `record` variable Visual Studio correctly shows type `Record`. If I try to access any members during runtime it of course throws an exception about non existing members. – Robert Koritnik Dec 30 '14 at 12:20
  • @MarcGravell: doing `typeof(Record).FullName` correctly returns my full type name including namespaces as it should. As stated in the question - all calls to this method work when generic type is passed on from caller's generic method type param. When I explicitly specify type in this call (correct one that should work as expected) it doesn't work. – Robert Koritnik Dec 30 '14 at 12:25
  • "all calls to this method work when generic type is passed on from caller's generic method type param." - do you mean as `Update(...)`? or `Update(...)`? – Marc Gravell Dec 30 '14 at 12:28
  • @MarcGravell: The first one... Type is being provided but is passed on from caller's generic type. Higher in the call stack this type is correctly inferred by compiler. – Robert Koritnik Dec 30 '14 at 12:35

2 Answers2

2

From what you are saying, including the comment:

If I try to access any members during runtime it of course throws an exception about non existing members.

it sounds like either a compiler bug or JIT bug. That means to diagnose it we need more knowledge of the compiler you are using (exact version) and/or the JIT you are using. In the case of a compiler bug, it may suffice if you can simply show the IL that has been generated, if you're familiar with that.

Note that the most recent VS preview includes an entirely new JIT (RyuJIT) and enables it by default system-wide, so if you have installed the VS preview, that would be my guess. If so, you can disable it trivially to check.


Note the other option here is something like a using alias that aliases Record to be System.String, or aliases var to be System.String (plus some implicit conversion operators, etc). These are unlikely, but I've seen it happen ;p

Edit: the above is ruled out by your comment:

doing typeof(Record).FullName correctly returns my full type name including namespaces as it should.

So we are left with compiler bug or JIT bug.


If this is a RyuJIT bug, here are the options to disable it (I suggest using the config one first, as it is the easiest to do):

  1. As an environment variable: set COMPLUS_useLegacyJit=1

  2. In the registry: Set HKLM or HKCU, Software\Microsoft.NETFramework. Key name: useLegacyJit. Type: REG_DWORD. Value: 1

  3. In an app.exe.config file:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <runtime>
        <useLegacyJit enabled="1" />
      </runtime>
    </configuration>
    

(these come from a separate discussion with MS that I had previously)

Siguza
  • 21,155
  • 6
  • 52
  • 89
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I'm using Visual Studio Premium 2013 Update 4. If I open VS2012 Command prompt (although I don't have VS 2012 installed and there's no matching command prompt for 2013) and type **csc** it says: *Microsoft (R) Visual C# Compiler version 4.0.30319.33440 for Microsoft (R) .NET Framework 4.5* – Robert Koritnik Dec 30 '14 at 12:31
  • @RobertKoritnik can you carve it down to a minimal bit of code that is reproducible? I would love to take a deeper look, but that demands a repro – Marc Gravell Dec 30 '14 at 12:35
  • I could try writing an example, but if it's a JIT bug it will be hard to repro such source code because if it was easy there would be many developers facing the same issue/bug. but I can try... Also mind that I'm (apparently) not using the latest compiler as I'm not using VS2015... – Robert Koritnik Dec 30 '14 at 12:38
  • 2
    @RobertKoritnik another fun thing to try: make the project explicit x86 (not AnyCPU) and try it, then make it explicit x64 and try it; does it fail in both? or just one? – Marc Gravell Dec 30 '14 at 12:39
  • @RobertKoritnik re "many developers" - no, that is not necessarily the case; JIT and compiler bugs can be *incredibly* contextual and specific - it is entirely possible that it is very very rare. I reported a RyuJIT bug a month or so ago: I'm pretty sure nobody else would have hit the same scenario for many months. – Marc Gravell Dec 30 '14 at 12:40
  • that was an excellent suggestion. Everything's fine in x86 but doesn't work in x64. :) What now? How should I mitigate this to keep my code work as expected? The thing is that my app is deployed on Azure which may use the same compiler or maybe not... :( Just for the info... If I check the **Optimize code** in x64 it also works, although I can't debug this particular method but I can debug downcalls and I can confirm they're being executed as expected. – Robert Koritnik Dec 30 '14 at 13:02
  • How can we pinpoint which part is buggy? Compiler or JIT? When I look at IL code (in ILSpy) seems fine to my eyes. But I'm no IL expert... Looking at compiled C# code in ILSpy seems fine. Should I be looking at it any differently to see whether compiler is buggy? – Robert Koritnik Dec 30 '14 at 13:36
  • @RobertKoritnik if you post the IL, I can tell you whether it is the compiler or the JIT ;p – Marc Gravell Dec 30 '14 at 14:06
  • 1
    @RobertKoritnik also, you've already answered this actually: the x64 vs x86 test is pretty much intended to help clarify this; it strongly suggests that the bug is in the x64 JIT; the IL should be the same either way. If it failed in both, then *probably* the compiler (although it could have been a bug that is common to both JITs, but that is rarer) – Marc Gravell Dec 30 '14 at 14:07
  • 1
    @RobertKoritnik in case this is a RyuJIT issue (perhaps from the azure tools), I've included (see edit) instructions for disabling it – Marc Gravell Dec 30 '14 at 14:17
1

If you're not getting a runtime type exception, I might suspect a bug in the Visual Studio debugger that I've run into. If you have two locally scoped variable with the same name in the same method, the debugger can confuse them:

private void DoSomething(int x)
{
    {
        var s = "STRING!";
        Console.WriteLine(s);
    }
    {
        var s = 5;
        Console.WriteLine(s);
    }
}

I don't know if the above example will actually reproduce the issue, but I've seen behavior from the debugger where you mouse over the second s and it shows it as "STRING!", not 5. That would be the first thing I would check, that you don't have another local variable in the same method named "record".

Bryce Wagner
  • 2,640
  • 1
  • 26
  • 43