본문 바로가기
C#

null-coalescing assignment 연산자(??=) 가 C# 8.0에서 추가된 문법이라, C# 7.3 컴파일러로는 사용할 수 없다는 뜻입니다.

by 공부봇 2025. 9. 11.
반응형

좋은 포인트예요. 오류 메시지는 null-coalescing assignment 연산자(??=) 가 C# 8.0에서 추가된 문법이라, C# 7.3 컴파일러로는 사용할 수 없다는 뜻입니다.

빠른 수정 (원인→해결)

  • 원인:
  • assemblies ??= new[] { Assembly.GetExecutingAssembly() }; // C# 8.0 문법
  • C# 7.3 호환 수정:
  • if (assemblies == null) assemblies = new[] { Assembly.GetExecutingAssembly() };

또한, 제가 드렸던 예시 중에 using var 도 C# 8.0 문법입니다. C# 7.3에서는 전통적인 using (...) { } 블록으로 바꿔주면 됩니다.


드롭인 패치: C# 7.3 호환 버전 메서드들

아래는 GuiLocalization의 문제되는 부분만 C# 7.3에 맞게 고쳐 쓴 코드입니다. (나머지 보조 메서드들은 그대로 사용하셔도 됩니다.)

1) LoadAuto(여러 어셈블리) – ??= 제거

public static void LoadAuto(string filePath, string languageCode, Assembly[] assemblies, string namespacePrefix = null)
{
    if (assemblies == null)
        assemblies = new[] { Assembly.GetExecutingAssembly() };

    var all = new List<Type>();
    foreach (var asm in assemblies)
    {
        all.AddRange(DiscoverContainers(asm, namespacePrefix));
    }
    LoadByTypes(filePath, languageCode, all);
}

2) LoadAuto(단일 어셈블리) – 동일

public static void LoadAuto(string filePath, string languageCode, Assembly assembly, string namespacePrefix = null)
{
    if (assembly == null)
        assembly = Assembly.GetExecutingAssembly();

    var candidates = DiscoverContainers(assembly, namespacePrefix);
    LoadByTypes(filePath, languageCode, candidates);
}

3) LoadByTypes – using var → 전통 using 블록

public static void LoadByTypes(string filePath, string languageCode, IEnumerable<Type> containerTypes,
                               int headerRowIndex = 0, int nameColumnIndex = 0)
{
    EnsureCodePagesProvider(); // .NET Core/5+에서 .xls 지원

    using (var stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    using (var reader = ExcelReaderFactory.CreateReader(stream))
    {
        var conf = new ExcelDataSetConfiguration
        {
            ConfigureDataTable = _ => new ExcelDataTableConfiguration { UseHeaderRow = false }
        };
        var ds = reader.AsDataSet(conf);

        var sheetMap = new Dictionary<string, DataTable>(StringComparer.OrdinalIgnoreCase);
        foreach (DataTable t in ds.Tables)
            sheetMap[NormalizeSheetName(t.TableName)] = t;

        foreach (var container in containerTypes)
        {
            var sheetName = container.Name;
            var structName = "tag" + container.Name;
            var staticFieldName = "g_st" + container.Name;

            DataTable table;
            if (!sheetMap.TryGetValue(sheetName, out table))
            {
                Debug.WriteLine("[WARN] Sheet '" + sheetName + "' not found. (skip)");
                continue;
            }

            if (table.Rows.Count == 0)
            {
                Debug.WriteLine("[WARN] Sheet '" + sheetName + "' is empty. (skip)");
                continue;
            }

            int langCol = FindLanguageColumnIndex(table, languageCode, headerRowIndex);
            if (langCol < 0)
            {
                Debug.WriteLine("[WARN] Sheet '" + sheetName + "': language '" + languageCode + "' not found in header row. (skip)");
                continue;
            }

            HashSet<string> dups;
            var map = BuildNameValueMap(table, langCol, headerRowIndex, nameColumnIndex, out dups);
            if (dups.Count > 0)
                Debug.WriteLine("[WARN] Sheet '" + sheetName + "': duplicated keys: " + string.Join(", ", dups) + " (last wins)");

            var structType = container.GetNestedType(structName, BindingFlags.Public | BindingFlags.NonPublic);
            if (structType == null)
            {
                Debug.WriteLine("[ERROR] Nested struct '" + structName + "' not found in '" + container.FullName + "'.");
                continue;
            }

            var staticField = container.GetField(staticFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
            if (staticField == null || staticField.FieldType != structType)
            {
                Debug.WriteLine("[ERROR] Static field '" + staticFieldName + "' not found or type mismatch in '" + container.FullName + "'.");
                continue;
            }

            AuditKeys(structType, map, sheetName);

            object boxed = Activator.CreateInstance(structType);
            foreach (var f in structType.GetFields(BindingFlags.Public | BindingFlags.Instance))
            {
                if (f.FieldType != typeof(string)) continue;
                string val;
                if (map.TryGetValue(f.Name, out val))
                    f.SetValue(boxed, val);
            }

            staticField.SetValue(null, boxed);
            Debug.WriteLine("[INFO] Applied: " + container.FullName + "." + staticFieldName + " from sheet '" + sheetName + "' (" + languageCode + ")");
        }
    }
}

그리고 BuildNameValueMap 시그니처를 C# 7.3 친화적으로 조금 바꿔줍니다(튜플 반환 대신 out 사용):

private static Dictionary<string, string> BuildNameValueMap(
    DataTable table, int langCol, int headerRowIndex, int nameCol, out HashSet<string> duplicates)
{
    var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
    duplicates = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

    int startRow = headerRowIndex + 1;
    for (int r = startRow; r < table.Rows.Count; r++)
    {
        var row = table.Rows[r];
        string name = GetCellString(row, nameCol);
        if (string.IsNullOrWhiteSpace(name)) continue;

        string val = GetCellString(row, langCol) ?? string.Empty;
        if (map.ContainsKey(name)) duplicates.Add(name);
        map[name] = val;
    }
    return map;
}

참고: 식 본문 멤버(=>), 문자열 보간($"{x}") 등은 C# 7.3에서도 사용 가능합니다.
여기서는 최소한의 변경으로 ??=/using var/튜플 반환만 7.3 호환으로 바꿨습니다.


대안: C# 언어 버전 올리기 (가능하면 추천)

프로젝트가 허용된다면 **언어 버전을 8.0+**로 올리면, 원래 코드 그대로 쓰실 수 있습니다.

  • SDK-style csproj:
  • <PropertyGroup> <LangVersion>8.0</LangVersion> <!-- 또는 latest --> </PropertyGroup>
  • Visual Studio: 프로젝트 속성 → Build → Advanced… → Language version

필요하시면 전체 파일(C# 7.3 대응본) 통으로 정리해서 드릴게요.

반응형