반응형
    
    
    
  좋은 포인트예요. 오류 메시지는 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 대응본) 통으로 정리해서 드릴게요.
반응형
    
    
    
  'C#' 카테고리의 다른 글
| 왜 IDisposable을 구현하나요? (0) | 2025.09.19 | 
|---|---|
| “직접 델리게이트 콜백” → “.NET 표준 이벤트 패턴(Event + EventArgs)” (0) | 2025.09.19 | 
| sealed를 왜 써야 할까? (0) | 2025.09.19 | 
| C#의 readonly 키워드 (0) | 2025.09.19 | 
| VisionThread/EmguVision 라이브·검사 공통 파이프라인 리팩토링 (풀코드) (0) | 2025.09.10 | 
| EmguImage 안에 IInputArray를 반환하는 getter (0) | 2025.09.09 | 
| private static bool IsFiniteFloat(float v) => !(float.IsNaN(v) || float.IsInfinity(v)); (0) | 2025.09.09 | 
| 소멸자(~EmguVision)로 GDI 리소스(Pen)를 해제하는 방식은 권장되지 않습니다. (0) | 2025.09.09 |