Использование API NDepend из сценария Windows PowerShell 5.1 приводит к ошибке регистра букв CLS

Я хотел бы использовать API NDepend из сценария Windows PowerShell 5.1, но это может быть невозможно из-за того, что Windows PowerShell не может обрабатывать свойства с тем же именем, но с другим регистром.

Вот код PowerShell, который работает на данный момент:

using namespace NDepend

Set-StrictMode -Version Latest

$AssList = "NDepend.API", "NDepend.Core", "NDepend.Platform.DotNet", "NDepend.Analysis", "NDepend.Platform.DotNet.NetFx", "NDepend.UI.NetFx"

$AssList.ForEach{
    $AssPfad = Join-Path -Path "D:\Program Files\NDepend\Lib" -ChildPath "$_.dll"
    Add-Type -Path $AssPfad
}

$ndprojPath = "D:\Test1234.ndproj"
# Still using NDepend in the namespace for clarity (although it is not necessary)
$ndprojPathAbsolute = [NDepend.Path.PathHelpers]::ToAbsoluteFilePath($ndprojPath)
$ndProvider = [NDepend.NDependServicesProvider]::new()
$ndManager = $ndProvider.ProjectManager
$project = $ndManager.LoadProject($ndprojPathAbsolute)
$outputDirPath = Join-Path -Path $PSScriptRoot -ChildPath "OutputDir"
$project.Properties.OutputDir = [NDepend.Path.PathHelpers]::ToDirectoryPath($outputDirPath)
$analysisResult = [NDepend.Analysis.ExtensionMethodsProjectAnalysis]::RunAnalysisAndBuildReport($project)
$ndCodebase = $analysisResult.CodeBase

Вывод $ndCodebase приводит к ошибке

format-default : # The field or property "F18r" for type "a.QHkY" differs only in letter casing from the field or property "f18R". The type must be Common Language Specification (CLS) compliant

Остальная часть скрипта работает и выводит результат анализа:

$Issues = [Analysis.ExtensionMethodsProjectAnalysis]::ComputeIssues($analysisResult)
$ndIssuesSet = $Issues 
$ndIssuesSet.AllRules

На данный момент я не знаю, как избежать этой ошибки.

PS: Код можно воспроизвести только при наличии лицензии на сборку.

Обновление: благодаря комментарию и предложениям mklement0 я смог задуматься о свойстве, вызывающем ошибку.

$Ass = [AppDomain]::CurrentDomain.GetAssemblies().Where{$_.GetName().Name -eq "NDepend.Core"}[0]
$t = $ass.gettype("a.QHkY", $True)
# Just for information purpose
$t.GetProperties().Where{$_.Name -match "f18r"}

# Not getting this property with reflection due to casing?
$PropInfo1 =  $ass.gettype("a.QHkY", $True).GetProperty("f18r", [System.Reflection.BindingFlags]::IgnoreCase) 
# Getting this property
$PropInfo2 =  $ass.gettype("a.QHkY", $True).GetProperty("F18r") 
# No value since $PropInfo1 is $null
$PropInfo1.GetValue($analysisResult.CodeBase)
# Getting a value
$PropInfo2.GetValue($analysisResult.CodeBase)

Но поскольку мне нужно значение свойства CodeBase:

# Get the value of the CodeBase property
$PropInfo = $ass.gettype("a.vHLF", $True).GetProperty("CodeBase")
$Value = $PropInfo.GetValue($ndCodebase)
$Value

Я получу ту же ошибку формата по умолчанию и вернусь к исходной точке. $Value содержит значение объекта CodeBase, но похоже, что ошибку вызывает форматтер PowerShell.

Обновление: как предположил mklement0, все сводится к тому, чтобы «воссоздать» объект как пользовательский тип и необходимые свойства.

Я сделал это для всех, кто заинтересован в решении с использованием кода C# внутри сценария Powershell. Но, в конце концов, это, вероятно, слишком много усилий.

using namespace NDepend

Set-StrictMode -Version Latest

$AssList = "NDepend.API", "NDepend.Core", "NDepend.Platform.DotNet", "NDepend.Analysis", "NDepend.Platform.DotNet.NetFx", "NDepend.UI.NetFx"

$AssList.ForEach{
    $AssPfad = Join-Path -Path "D:\Program Files\NDepend\Lib" -ChildPath "$_.dll"
    Add-Type -Path $AssPfad
}

$ndprojPath = "D:\Test.ndproj"
# Still using NDepend in the namespace for clarity (although it is not necessary)
$ndprojPathAbsolute = [Path.PathHelpers]::ToAbsoluteFilePath($ndprojPath)
$ndProvider = [NDepend.NDependServicesProvider]::new()
$ndManager = $ndProvider.ProjectManager
$project = $ndManager.LoadProject($ndprojPathAbsolute)
$analysisResult = [NDepend.Analysis.ExtensionMethodsProjectAnalysis]::RunAnalysis($project)

# Reflection done in C# to avoid dealing with a CodeBase object directly in PowerShell
$CSCode = @"
using NDepend.Analysis;
using NDepend.Issue;
using NDepend.CodeModel;

using System;
using System.Collections.Generic;
using System.Linq;

public class CodeBaseInfo
{
    public ICodeBase CodeBase { get; set; }
    public int NbLinesOfCode { get; set; }

    public CodeBaseInfo(IAnalysisResult analysisResult)
    {
        this.CodeBase = analysisResult.CodeBase;
        var ass = AppDomain.CurrentDomain.GetAssemblies().Where(a=>a.GetName().Name == "NDepend.Core").First();
        var propInfo = ass.GetType("a.vHLF").GetProperty("CodeBase");
        var propValue = propInfo.GetValue(analysisResult); 
        propInfo = ass.GetType("a.QHkY").GetProperty("NbLinesOfCode");
        this.NbLinesOfCode = Convert.ToInt32(propInfo.GetValue(propValue));
    }

    // Will result to the format error - only for demonstration purposes
    public ICodeBase GetCodeBase()
    {
        return this.CodeBase;
    }

    public ICodeBaseView GetApplication() {
        return this.CodeBase.Application;
    }

    public int GetNbLinesOfCode() {
        return this.NbLinesOfCode;
    }
}
"@

# Please check the path of the NDepend.API.dll
Add-Type -TypeDefinition $CSCode -ReferencedAssemblies "D:\Program Files\NDepend\Lib\NDepend.API.dll", "netstandard" 
$CodeBaseInfo = [CodeBaseInfo]::new($analysisResult)
# Same format error
# $CodeBaseInfo.CodeBase
# This works...
$CodeBaseInfo.GetApplication()
$CodeBaseInfo.GetNbLinesOfCode()

Я обновил свой ответ, чтобы прояснить следующее: проблема возникает всякий раз, когда вы получаете доступ к любому члену экземпляра, тип которого имеет члены с именами, которые являются вариациями регистра друг друга. Поскольку форматирование PowerShell для отображения перечисляет элементы, когда вы (неявно) выводите экземпляр (например, отправляете $Value отдельно), тогда возникает ошибка. Таким образом, вы не сможете работать с такими экземплярами целиком: вы можете обращаться к их свойствам и полям (и вызывать их методы) только по одному, посредством отражения.

mklement0 26.08.2024 12:51

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

mklement0 26.08.2024 12:54

Спасибо за этот момент, это из-за обфускации, мы проверяем, можно ли избежать такого конфликта имен.

Patrick from NDepend team 26.08.2024 13:35
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Действительно, PowerShell явно проверяет типы .NET на наличие членов, имена которых являются вариациями регистра друг друга (например, F18r против f18r); любой такой тип по определению не CLS-совместим.

  • В Windows PowerShell и, по крайней мере, до версии 7.4.x, а также в PowerShell (Core) 7 обнаружение изменения регистра в именах свойств или полей приводит к ошибке (завершающей оператор) (исключение .NET) при доступе к любому из членов данного типа предпринимается попытка (в то время как в отношении методов такие вариации регистра спокойно допускаются - при условии, что также нет вариаций регистра свойств/полей - но пока один из них вызывается, вы не можете предсказуемо вызвать данный вариант) .

  • Проблема GitHub #10303 — это предложение, получившее зеленый свет по устранению этой проблемы, но никто еще не приступил к его реализации.

    • Как только предложение будет реализовано, обе попытки .F18r и .f18r будут успешными и получат доступ к свойству/полю, которое точно соответствует регистру, но вариант, который не соответствует точно, все равно будет сообщать об ошибке, например. .F18R.

Обходной путь:

На данный момент единственный способ избежать ошибок в этом контексте — использовать отражение .NET при доступе к членам свойств и полей, которые имеют вариации регистра.

  • Обратите внимание, что к такому свойству и полям можно получить неявный доступ (и, следовательно, вызвать ошибки) при выводе экземпляров их типов на хост (отображение) благодаря богатой системе форматирования PowerShell for-display , которая по умолчанию представляет собой непримитивный . NET по их общедоступным свойствам и полям — вот почему при выводе $ndCodebase (опираясь на поведение PowerShell неявного вывода) у вас возникла ошибка.

  • Вам необходимо знать конкретный тип данного экземпляра, чтобы использовать обходной путь отражения, поскольку метод отражения должен быть вызван для типа, чтобы избежать ошибки - тип интерфейса может работать, а может и не работать (в то время как вызов любого члена в экземпляр, включая метод .GetType(), неизменно вызывает возникшую ошибку).

    • Этот тип необходимо использовать, потому что даже вызов .GetType() в экземпляре вызовет возникшую ошибку, поскольку ошибку вызывает любой доступ к члену экземпляра (тогда как построение экземпляра в целом работает нормально).
  • В вашем случае в документации типы значений свойств выражаются только как интерфейсы, а интерфейс, который реализует значение свойства .CodeBase, по-видимому, не охватывает элементы с вариантами регистра, которые вызывают ошибку.

    • Однако у вас должна быть возможность определить полный тип конкретного типа с помощью внутреннего свойства .pstypenames:

      [Type] $concretePublicType = 
        $ndCodebase.pstypenames.Where({ ($_ -as [type]).IsPublic }, 'First')[0]
      

Используя полный конкретный тип, вы можете использовать отражение для доступа, например, к значению поля/свойства .F18r (точно по регистру) следующим образом, предполагая, что это свойство (если это поле, замените .GetProperty() на .GetField():

$concretePublicType.GetProperty('F18r').GetValue($ndCodebase)

Другие вопросы по теме