Код С#, кажется, оптимизирован недействительным способом, так что значение объекта становится нулевым

У меня есть следующий код, который вызывает странную проблему:

var all = new FeatureService().FindAll();
System.Diagnostics.Debug.Assert(all != null, "FindAll must not return null");
System.Diagnostics.Debug.WriteLine(all.ToString()); // throws NullReferenceException

Подпись метода FindAll:

public List<FeatureModel> FindAll()

Выполняя код, я подтвердил, что возвращаемое значение из FindAll не равно null, и, как вы можете видеть из Assert, переменная "all" не является нулевой, но в следующей строке она выглядит пустой.

Проблема не связана с ошибкой при вызове метода ToString(). Я упростил его до этого воспроизводимого примера, пытаясь проследить основную причину.

Это может быть ключ: в отладчике переменная "все" появляется в окне "Локали" со значением "Невозможно получить значение локального или аргумента" все ", поскольку оно недоступно в этом указателе инструкции, возможно, потому что он был оптимизирован".

Я рассмотрел попытку одного из подходов, задокументированных в другом месте для отключения оптимизации кода, но это не решило бы проблему, поскольку версия версии кода будет по-прежнему оптимизирована.

Я использую Visual Studio 2010 с .NET 4.0.

Любые мысли?

UPDATE: для каждого запроса, вот весь метод:

protected override List<FeatureModel> GetModels() {
    var all = new FeatureService().FindAll();
    var wr = new WeakReference(all);
    System.Diagnostics.Debug.Assert(all != null, "FindAll must not return null");
    System.Diagnostics.Debug.WriteLine(wr.IsAlive);
    System.Diagnostics.Debug.WriteLine(all.ToString()); // throws NullReferenceException
    return all;
}

Как FYI, первоначальная реализация была просто:

protected override List<FeatureModel> GetModels() {
    return new FeatureService().FindAll();
}

Я первоначально столкнулся с нулевым исключением в вызывающем методе. Код, который я опубликовал, после некоторого времени отслеживал проблему.

UPDATE # 2: В соответствии с запросом, вот трассировка стека из исключения:

 at FeatureCrowd.DomainModel.FeatureSearch.GetModels() in C:\Users\Gary\Documents\Visual Studio 2010\Projects\FeatureCrowd\FeatureCrowd.DomainModel\FeatureSearch.cs:line 32
 at FeatureCrowd.DomainModel.FeatureSearch.CreateIndex() in C:\Users\Gary\Documents\Visual Studio 2010\Projects\FeatureCrowd\FeatureCrowd.DomainModel\FeatureSearch.cs:line 42
 at FeatureCrowd.DomainModel.FeatureService.CreateSearchIndex() in C:\Users\Gary\Documents\Visual Studio 2010\Projects\FeatureCrowd\FeatureCrowd.DomainModel\FeatureService.cs:line 100
 at Website.MvcApplication.BuildLuceneIndexThread(Object sender) in C:\Users\Gary\Documents\Visual Studio 2010\Projects\FeatureCrowd\FeatureCrowd.Website\Global.asax.cs:line 50
 at Website.MvcApplication.Application_Start() in C:\Users\Gary\Documents\Visual Studio 2010\Projects\FeatureCrowd\FeatureCrowd.Website\Global.asax.cs:line 61

Ответы

Ответ 1

После того, как Лассе обнаружил, что метод FindAll генерирует неправильный IL, я наткнулся на другой метод, который также генерировал неправильный IL-I, также я нашел основную причину и разрешение.

Соответствующая строка во втором методе:

var policy = Cache.GetDefaultCacheItemPolicy(dependentKeys, true);

Кэш - это мой собственный объект. Метод GetDefaultCacheItemPolicy возвращает объект System.Runtime.Caching.CacheItemPolicy. Сгенерированный ИЛ, однако, выглядел так:

Func<Feature, FeatureModel> policy = (Func<Feature, FeatureModel>) base.Cache.GetDefaultCacheItemPolicy(dependentKeys, true);

Здесь есть два проекта. Методы, генерирующие неправильный IL, находятся в одном проекте под названием DomainModel, а объект Cache - в проекте Utilities, на который ссылается первый. Второй проект содержит ссылку на System.Runtime.Caching, но первая не делает.

Исправить было добавление ссылки на System.Runtime.Caching для первого проекта. Теперь сгенерированный IL выглядит правильно:

CacheItemPolicy policy = base.Cache.GetDefaultCacheItemPolicy(dependentKeys, true);

Первый метод (который Лассе опубликовал в своем ответе) теперь также генерирует правильный ИЛ.

Ура!

Ответ 2

После просмотра кода через TeamViewer и, наконец, загрузки, компиляции и запуска кода на моем собственном компьютере, я верю, это случай ошибки компилятора в С# 4.0.


Я отправил вопрос с запросом на проверку, после того как вы решили уменьшить проблему до нескольких простых проектов и файлов. Он доступен здесь: Возможная ошибка компилятора С# 4.0, могут ли другие проверить?


Вероятным виновником является не этот метод:

protected override List<FeatureModel> GetModels() {
    var fs = new FeatureService();
    var all = fs.FindAll();
    var wr = new WeakReference(all);
    System.Diagnostics.Debug.Assert(all != null, "FindAll must not return null");
    System.Diagnostics.Debug.WriteLine(wr.IsAlive);
    System.Diagnostics.Debug.WriteLine(all.ToString()); // throws NullReferenceException
    return all;
}

Но метод, который он вызывает, FeatureService.FindAll:

public List<FeatureModel> FindAll() {
    string key = Cache.GetQueryKey("FindAll");
    var value = Cache.Load<List<FeatureModel>>(key);
    if (value == null) {
        var query = Context.Features;
        value = query.ToList().Select(x => Map(x)).ToList();
        var policy = Cache.GetDefaultCacheItemPolicy(value.Select(x => Cache.GetObjectKey(x.Id.ToString())), true);
        Cache.Store(key, value, policy);
    }
    value = new List<FeatureModel>();
    return value;
}

Если я изменил вызов в GetModels с помощью этого:

var all = fs.FindAll();

:

var all = fs.FindAll().ToList(); // remember, it already returned a list

то программа выйдет из строя с помощью ExecutionEngineException.


После выполнения очистки, сборки, а затем просмотра скомпилированного кода через Reflector, вот как выглядит вывод (прокрутите в нижней части кода для важной части):

public List<FeatureModel> FindAll()
{
    List<FeatureModel> value;
    Func<FeatureModel, string> CS$<>9__CachedAnonymousMethodDelegate6 = null;
    List<FeatureModel> CS$<>9__CachedAnonymousMethodDelegate7 = null;
    string key = base.Cache.GetQueryKey("FindAll");
    if (base.Cache.Load<List<FeatureModel>>(key) == null)
    {
        if (CS$<>9__CachedAnonymousMethodDelegate6 == null)
        {
            CS$<>9__CachedAnonymousMethodDelegate6 = (Func<FeatureModel, string>) delegate (Feature x) {
                return this.Map(x);
            };
        }
        value = base.Context.Features.ToList<Feature>().Select<Feature, FeatureModel>(((Func<Feature, FeatureModel>) CS$<>9__CachedAnonymousMethodDelegate6)).ToList<FeatureModel>();
        if (CS$<>9__CachedAnonymousMethodDelegate7 == null)
        {
            CS$<>9__CachedAnonymousMethodDelegate7 = (List<FeatureModel>) delegate (FeatureModel x) {
                return base.Cache.GetObjectKey(x.Id.ToString());
            };
        }
        Func<Feature, FeatureModel> policy = (Func<Feature, FeatureModel>) base.Cache.GetDefaultCacheItemPolicy(value.Select<FeatureModel, string>((Func<FeatureModel, string>) CS$<>9__CachedAnonymousMethodDelegate7), true);
        base.Cache.Store<List<FeatureModel>>(key, value, (CacheItemPolicy) policy);
    }
    value = new List<FeatureModel>();
    bool CS$1$0000 = (bool) value;
    return (List<FeatureModel>) CS$1$0000;
}

Обратите внимание на три последние строки метода, вот что они выглядят в коде:

value = new List<FeatureModel>();
return value;

вот что говорит рефлектор:

value = new List<FeatureModel>();
bool CS$1$0000 = (bool) value;
return (List<FeatureModel>) CS$1$0000;

Создает список, затем переводит его в логическое, затем возвращает его в список и возвращает. Скорее всего, это вызывает проблему стека.

Здесь тот же метод, в IL (еще через Reflector), я удалил большую часть кода:

.method public hidebysig instance class [mscorlib]System.Collections.Generic.List`1<class FeatureCrowd.DomainModel.FeatureModel> FindAll() cil managed
{
    .maxstack 5
    .locals init (
        [0] string key,
        [1] class [mscorlib]System.Collections.Generic.List`1<class FeatureCrowd.DomainModel.FeatureModel> 'value',
        [2] class [System.Data.Entity]System.Data.Objects.ObjectSet`1<class FeatureCrowd.DomainModel.Feature> query,
        [3] class [mscorlib]System.Func`2<class FeatureCrowd.DomainModel.Feature, class FeatureCrowd.DomainModel.FeatureModel> policy,
        [4] class [mscorlib]System.Func`2<class FeatureCrowd.DomainModel.FeatureModel, string> CS$<>9__CachedAnonymousMethodDelegate6,
        [5] class [mscorlib]System.Collections.Generic.List`1<class FeatureCrowd.DomainModel.FeatureModel> CS$<>9__CachedAnonymousMethodDelegate7,
        [6] bool CS$1$0000,
        [7] char CS$4$0001)
    ...
    L_009f: newobj instance void [mscorlib]System.Collections.Generic.List`1<class FeatureCrowd.DomainModel.FeatureModel>::.ctor()
    L_00a4: stloc.1 
    L_00a5: ldloc.1 
    L_00a6: stloc.s CS$1$0000
    L_00a8: br.s L_00aa
    L_00aa: ldloc.s CS$1$0000
    L_00ac: ret 
}

Здесь screencast, показывающий сеанс отладки, если вы просто хотите выход Reflector, пропустите примерно до 2:50.

Ответ 3

Слева для потомков, это не проблема.

Смотрите новый новый ответ.


Вот что я верю.

Вопреки тому, что вы говорите, я считаю, что программа на самом деле не рушится ни в одной из строк, но вместо этого происходит сбой на одной из строк, следующих за ними, которые вы не разместили.

Причина, по которой я верю, в том, что я также считаю, что вы делаете Release-build, и в этом случае обе линии Debug будут удалены, так как они отмечены атрибутом [Conditional("DEBUG")].

Ключ здесь состоит в том, что переменная all была оптимизирована, и это должно произойти только во время сборки Release-build, а не в Debug-build.

Иными словами, я считаю, что переменная all является actull null, а строки Debug не выполняются, потому что они не скомпилированы в сборку. Отладчик покорно сообщает, что переменная all больше не существует.

Обратите внимание, что все это должно быть легко протестировано. Просто разместите точку останова на первой из двух строк Debug, которые вы разместили. Если точка останова поражена, моя гипотеза, скорее всего, неверна. Если это не так (и я собираюсь предположить, что символ точки останова отображается как полый круг во время выполнения), то эти строки не компилируются в сборку.

Ответ 4

Если вы подозреваете, что переменная каким-то образом оптимизирована, вы можете проверить это, используя либо через IsAlive свойство WeakReference для вашего объекта all, либо через GC.KeepAlive(all). Я не уверен, что это будет полезно, но, возможно, стоит попробовать.

Другая возможность, хотя и маловероятна, заключается в том, что по какой-то причине List<FeatureModel>.ToString выбрасывает это исключение. Вы можете проверить это с помощью инструмента, такого как .NET Reflector, чтобы узнать, что именно делает этот метод.