C# と VB.NET の質問掲示板

わんくま同盟

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

ツリー一括表示

C#(COM)+ExcelVBAで既定のプロパティを2つ定義 /ded (25/04/24(Thu) 15:34) #103641
Re[1]: C#(COM)+ExcelVBAで既定のプロパティを2つ定義 /魔界の仮面弁士 (25/04/24(Thu) 17:27) #103642
  └ Re[2]: C#(COM)+ExcelVBAで既定のプロパティを2つ定義 /ded (25/04/24(Thu) 18:20) #103643 解決済み
    └ Re[3]: C#(COM)+ExcelVBAで既定のプロパティを2つ定義 /魔界の仮面弁士 (25/04/25(Fri) 14:09) #103644 解決済み
      └ Re[4]: C#(COM)+ExcelVBAで既定のプロパティを2つ定義 /ded (25/04/25(Fri) 16:13) #103645 解決済み
        └ Re[5]: C#(COM)+ExcelVBAで既定のプロパティを2つ定義 /ded (25/04/25(Fri) 16:35) #103646 解決済み


親記事 / ▼[ 103642 ]
■103641 / 親階層)  C#(COM)+ExcelVBAで既定のプロパティを2つ定義
□投稿者/ ded (1回)-(2025/04/24(Thu) 15:34:25)

分類:[C#] 

属性(Attribute)あたりで出来そうな気がするのですが、調べ切れていないので教えてください。
よろしくお願いします。
--- 環境
VS 2022 Pro 17.13.6
C# 13(理由は後述)
Framework 4.7.2
対象プラットフォーム x86
COM 相互運用機能の登録はチェック済み
Excel(x86) Microsoft 365 Apps for enterprise

--- Book1.xlsm
Private Sub CommandButton1_Click()
    Dim cls As Object
    
    On Error GoTo Catch
    Set cls = CreateObject("ClassLibrary1.Class1")
    
    MsgBox cls.Count    ' 2
    MsgBox cls(0)       ' AIUEO
    MsgBox cls(1)       ' 123
    MsgBox cls.Item_2("name")  ' AIUEO  ' デフォルトインデクサ or 既定のプロパティ にしたい
    
    Call cls.Add("hoge", "hoo")
    MsgBox cls.Count    ' 3
    MsgBox cls(2)       ' hoo
    MsgBox cls.Item_2("hoge")  ' hoo
    
    cls(2) = "bar"
    MsgBox cls(2)       ' bar
    MsgBox cls.Item_2("hoge")  ' bar  ' デフォルトインデクサ or 既定のプロパティ にしたい
    
    GoTo Finally
Catch:
    MsgBox "(" & Err.Number & ")" & Err.Description
    
Finally:
    Set cls = Nothing
    
End Sub


--- Class1.cs
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace ClassLibrary1;

[ComVisible(true)]
[Guid("C6D56925-BF75-47C5-A0A2-2359FF021ADF")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IClass1
{
    public object this[short idx] { get; set; }
    public object this[string key] { get; set; }

    public void Add(string key, object obj);

    public short Count { get; }
}

[ComVisible(true)]
[Guid("BF5F036C-7602-4CEF-BE69-F1E22A463087")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Class1:IClass1
{
    private Dictionary<string, object> list = new Dictionary<string, object>();

    public Class1()
    {
        // test data
        list.Add("name", "AIUEO");
        list.Add("num", (short)123);
    }

    public object this[short idx]
    {
        get { return list.Skip(idx).First().Value; }
        set { list[list.Skip(idx).First().Key] = value; }
    }
    public object this[string key]
    {
        get { return list[key]; }
        set { list[key] = value; }
    }

    public void Add(string key, object value) => list.Add(key, value);

    public short Count => (short)list.Count;

}


---
属性 'ClassInterface' はこの宣言型では無効です。'アセンブリ, クラス' 宣言でのみ有効です。
C# 7.3 では、修飾子 'public' はこの項目に対して有効ではありません。'8.0' 以上の言語バージョンをご使用ください。
C# 7.3 では、修飾子 'public' はこの項目に対して有効ではありません。'8.0' 以上の言語バージョンをご使用ください。

 ⇒上記の点からC# 12に変更。変更箇所はClassLibrary1.csproj参照のこと

--- ClassLibrary1.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{77A4FFE1-6014-4FFD-B508-A7CD4991D732}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>ClassLibrary1</RootNamespace>
    <AssemblyName>ClassLibrary1</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <LangVersion>12</LangVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
  




[ □ Tree ] 返信 編集キー/

▲[ 103641 ] / ▼[ 103643 ]
■103642 / 1階層)  Re[1]: C#(COM)+ExcelVBAで既定のプロパティを2つ定義
□投稿者/ 魔界の仮面弁士 (3839回)-(2025/04/24(Thu) 17:27:28)
No103641 (ded さん) に返信
> VS 2022 Pro 17.13.6
> C# 13(理由は後述)
> Framework 4.7.2

C# 13 は本来、.NET 9 以降向けのものです。理由にもよりますが、
.NET Framework の場合は、既定の C# 7.3 までの機能を使った方が良いですよ。


.NET Framework であっても、条件さえ満たせば、C# 8 以降の機能(の一部)を
一応利用できますが、中には .NET Framework で呼び出そうとすると、
パフォーマンスを十分に発揮できないものもあるので…(特に ref 関連)。


> C# 7.3 では、修飾子 'public' はこの項目に対して有効ではありません。'8.0' 以上の言語バージョンをご使用ください。
何のために、interface のメンバーに public スコープを指定しているのでしょうか?

インターフェイスメンバーのスコープ指定(8.0)や
ファイルスコープ名前空間(10.0)の部分を見直せば、
充分に 7.3 の文法だけでコーディングできますよね。


> public void Add(string key, object obj);
> public void Add(string key, object value) => list.Add(key, value);
インターフェイスと実装で、引数名を変更しているのは何故ですか?
COM インターフェイスの場合、名前付き引数での呼び出しがあるため、
引数名の変更はバイナリ互換性維持の観点から望ましくありません。

まぁ、バージョンアップ時にコクラス側の引数名を変更したわけではないので、直ちに問題になるわけではないですが。



> public object this[short idx]
> public object this[string key]
COM の仕様上、オーバーロードはサポートされていません。
そのため、自動的に別名が付与されることになります。
(名前やディスパッチ ID の異なる別名メンバーとなる)

short と string の両方を許可したい場合には、
Variant 引数(C# 的には object あるいは dynamic)で受け取り、
実行時判定で引数の型をチェックするようにします。
たとえばこんな感じ。

[ComVisible(false)]
public object this[short idx]
{
 get { return list.Skip(idx).First().Value; }
 set { list[list.Skip(idx).First().Key] = value; }
}

[ComVisible(false)]
public object this[string key]
{
 get { return list[key]; }
 set { list[key] = value; }
}

[ComVisible(true)]
public object this[dynamic variant]
{
 get
 {
  if (variant is short idx) // int や byte もサポートした方が良いかも
  {
   return this[idx];
  }
  else if (variant is string key)
  {
   return list[key];
  }
  else
  {
   throw new ArgumentOutOfRangeException(nameof(variant));
  }
 }
 set
 {
  if (variant is short idx)
  {
   this[idx] = value;
  }
  else if (variant is string key)
  {
   list[key] = value;
  }
  else
  {
   throw new ArgumentOutOfRangeException(nameof(variant));
  }
 }
}
[ 親 103641 / □ Tree ] 返信 編集キー/

▲[ 103642 ] / ▼[ 103644 ]
■103643 / 2階層)  Re[2]: C#(COM)+ExcelVBAで既定のプロパティを2つ定義
□投稿者/ ded (2回)-(2025/04/24(Thu) 18:20:36)
2025/04/24(Thu) 18:29:14 編集(投稿者)
2025/04/24(Thu) 18:28:26 編集(投稿者)

完璧でした!
--- 以下返信

> C# 13 は本来、.NET 9 以降向けのものです。理由にもよりますが、
> .NET Framework の場合は、既定の C# 7.3 までの機能を使った方が良いですよ。

理由としては.Netのライブラリと共通部分があるのとコードが楽なので、めいっぱいコンパイラバージョン上げちゃってます。
とはいえせっかく教えて頂いたので、プリプロセッサで切り替えます。

> 何のために、interface のメンバーに public スコープを指定しているのでしょうか?
ここは癖ですね。C#13を指定していた関係で忘れてました。

> インターフェイスと実装で、引数名を変更しているのは何故ですか?
> COM インターフェイスの場合、名前付き引数での呼び出しがあるため、
> 引数名の変更はバイナリ互換性維持の観点から望ましくありません。
ここは完全にミニマムコードを作成する時のコピペミスです。

> COM の仕様上、オーバーロードはサポートされていません。
> そのため、自動的に別名が付与されることになります。
> (名前やディスパッチ ID の異なる別名メンバーとなる)
やはりそうでしたか。探し方が悪いのかと思ってました。

結果的に今回は下記の様にします。
回答ありがとうございました。
--- ※ switch〜case〜when部は試していません。
        public object this[object variant]
        {
            get
            {
                object Result = null;

#if NET
                switch (variant.GetType())
                {
                    case var b when b == typeof(byte):
                    case var s when s == typeof(short):
                    case var i when i == typeof(int):
                        Result = list.Skip((int)variant).FirstOrDefault().Value;
                        break;
                    case var str when str == typeof(string):
                        Result = list[(string)variant];
                        break;
                }
#else
                if (variant is string key) Result = list[key];
                else if (variant is byte idx_b) Result = list.Skip(idx_b).FirstOrDefault().Value;
                else if (variant is short idx_s) Result = list.Skip(idx_s).FirstOrDefault().Value;
                else if (variant is int idx_i) Result = list.Skip(idx_i).FirstOrDefault().Value;
                else throw new ArgumentOutOfRangeException(nameof(variant));
#endif

                return Result;
            }
            set
            {
                // .Net コード略

                if (variant is string key) list[key] = value;
                else if (variant is byte idx_b) list[list.Skip(idx_b).FirstOrDefault().Key] = value;
                else if (variant is short idx_s) list[list.Skip(idx_s).FirstOrDefault().Key] = value;
                else if (variant is int idx_i) list[list.Skip(idx_i).FirstOrDefault().Key] = value;
                else throw new ArgumentOutOfRangeException(nameof(variant));
            }
        }
---


■No103642 (魔界の仮面弁士 さん) に返信
> ■No103641 (ded さん) に返信
>>VS 2022 Pro 17.13.6
>>C# 13(理由は後述)
>>Framework 4.7.2
> 
> C# 13 は本来、.NET 9 以降向けのものです。理由にもよりますが、
> .NET Framework の場合は、既定の C# 7.3 までの機能を使った方が良いですよ。
> 
> 
> .NET Framework であっても、条件さえ満たせば、C# 8 以降の機能(の一部)を
> 一応利用できますが、中には .NET Framework で呼び出そうとすると、
> パフォーマンスを十分に発揮できないものもあるので…(特に ref 関連)。
> 
> 
>>C# 7.3 では、修飾子 'public' はこの項目に対して有効ではありません。'8.0' 以上の言語バージョンをご使用ください。
> 何のために、interface のメンバーに public スコープを指定しているのでしょうか?
> 
> インターフェイスメンバーのスコープ指定(8.0)や
> ファイルスコープ名前空間(10.0)の部分を見直せば、
> 充分に 7.3 の文法だけでコーディングできますよね。
> 
> 
>>public void Add(string key, object obj);
>>public void Add(string key, object value) => list.Add(key, value);
> インターフェイスと実装で、引数名を変更しているのは何故ですか?
> COM インターフェイスの場合、名前付き引数での呼び出しがあるため、
> 引数名の変更はバイナリ互換性維持の観点から望ましくありません。
> 
> まぁ、バージョンアップ時にコクラス側の引数名を変更したわけではないので、直ちに問題になるわけではないですが。
> 
> 
> 
>>public object this[short idx]
>>public object this[string key]
> COM の仕様上、オーバーロードはサポートされていません。
> そのため、自動的に別名が付与されることになります。
> (名前やディスパッチ ID の異なる別名メンバーとなる)
> 
> short と string の両方を許可したい場合には、
> Variant 引数(C# 的には object あるいは dynamic)で受け取り、
> 実行時判定で引数の型をチェックするようにします。
> たとえばこんな感じ。
> 

解決済み
[ 親 103641 / □ Tree ] 返信 編集キー/

▲[ 103643 ] / ▼[ 103645 ]
■103644 / 3階層)  Re[3]: C#(COM)+ExcelVBAで既定のプロパティを2つ定義
□投稿者/ 魔界の仮面弁士 (3840回)-(2025/04/25(Fri) 14:09:33)
No103643 (ded さん) に返信
> public object this[object variant]

回答しておいてなんですが、引数の名前を variant にしたのは、
Excel VBA 的には都合が悪かったかも知れません。
別の名前の方が良かったかも。


> --- ※ switch〜case〜when部は試していません。

switch ステートメントではなく、switch 式で書いてみた案。
型判定後の追加キャストを省けます。

return variant switch
{
 string key => list[key],
 short idx => list.Skip(idx).FirstOrDefault().Value,
 byte idx => list.Skip(idx).FirstOrDefault().Value,
 int idx => list.Skip(idx).FirstOrDefault().Value,
 _ => throw new ArgumentOutOfRangeException(nameof(variant))
};



それにしても、今回の要件で Skip を使うのってどうなのかな…。

OrderedDictionary<,> (.NET 9 から登場) であれば、
要素を追加した順番を維持した状態で列挙できることが保証されていますが、
Dictionary<,> では列挙順を保証していないはずなので…。
(まぁ、大抵は登録順に列挙されるのですけれども)


それから、存在しない番号やキーを指定した場合の動作が少々疑問。

最初の投稿 No103641 では、.First 指定だったので「無ければ実行時エラー」として統一されていたのに
結果報告 No103643 では .FirstOrDefault 指定に変わってしまっていて、ちぐはぐな印象を受けました。

.FirstOrDefault からは default(KeyValuePair<string, short>) 相当が得られるだけなので、
それだけならばエラーになりませんが、その Key を使ってアクセスしようとしたら
null キーとしてエラーになってしまい、FirstOrDefault に変更した意味が感じられませんでした。
例外を発生させたくないという意図があるのなら、list[何某] ではなく
list.TryGetValue(何某, out var item) を使うべきじゃないかな…と。
解決済み
[ 親 103641 / □ Tree ] 返信 編集キー/

▲[ 103644 ] / ▼[ 103646 ]
■103645 / 4階層)  Re[4]: C#(COM)+ExcelVBAで既定のプロパティを2つ定義
□投稿者/ ded (3回)-(2025/04/25(Fri) 16:13:17)
No103644 (魔界の仮面弁士 さん) に返信
> ■No103643 (ded さん) に返信
>>public object this[object variant]
>
> 回答しておいてなんですが、引数の名前を variant にしたのは、
> Excel VBA 的には都合が悪かったかも知れません。
> 別の名前の方が良かったかも。
本番コードにはvalueにしてます。

>>--- ※ switch〜case〜when部は試していません。
>
> switch ステートメントではなく、switch 式で書いてみた案。
> 型判定後の追加キャストを省けます。
>
> return variant switch
> {
>  string key => list[key],
>  short idx => list.Skip(idx).FirstOrDefault().Value,
>  byte idx => list.Skip(idx).FirstOrDefault().Value,
>  int idx => list.Skip(idx).FirstOrDefault().Value,
>  _ => throw new ArgumentOutOfRangeException(nameof(variant))
> };
switch 式はC#8だったような。
でもありですね。

> それにしても、今回の要件で Skip を使うのってどうなのかな…。
>
> OrderedDictionary<,> (.NET 9 から登場) であれば、
> 要素を追加した順番を維持した状態で列挙できることが保証されていますが、
> Dictionary<,> では列挙順を保証していないはずなので…。
> (まぁ、大抵は登録順に列挙されるのですけれども)
これはミニマムコードにした時の選択ミスです。
最初List<object>で、オーバーロード発生しないじゃぁDictionaryって感じです。
本番コードではxxxColectionで、NameとValueを持っているのは同じです。

> それから、存在しない番号やキーを指定した場合の動作が少々疑問。
>
> 最初の投稿 No103641 では、.First 指定だったので「無ければ実行時エラー」として統一されていたのに
> 結果報告 No103643 では .FirstOrDefault 指定に変わってしまっていて、ちぐはぐな印象を受けました。
確かにFirstが正解です。なんでFirstOrDefault にしちゃったんでしょう?<自分でもわかりません^^;
本番コードはFirstにしました。

> .FirstOrDefault からは default(KeyValuePair<string, short>) 相当が得られるだけなので、
> それだけならばエラーになりませんが、その Key を使ってアクセスしようとしたら
> null キーとしてエラーになってしまい、FirstOrDefault に変更した意味が感じられませんでした。
> 例外を発生させたくないという意図があるのなら、list[何某] ではなく
> list.TryGetValue(何某, out var item) を使うべきじゃないかな…と。
存在しない番号やキーは例外が正解でした。
指摘ありがとうございます。本番コードでFirstOrDefault 使うところでした^^;

解決済み
[ 親 103641 / □ Tree ] 返信 編集キー/

▲[ 103645 ] / 返信無し
■103646 / 5階層)  Re[5]: C#(COM)+ExcelVBAで既定のプロパティを2つ定義
□投稿者/ ded (4回)-(2025/04/25(Fri) 16:35:47)
swichのパターンマッチングってC#7だった...
---
        static void Main(string[] args)
        {
            hoge(123);
            hoge((short)777);
            hoge("aiueo");
        }

        static object hoge(object value)
        {
            var Result = new object();

            switch (true)
            {
                case true when value.GetType() == typeof(byte):
                case true when value.GetType() == typeof(short):
                case true when value.GetType() == typeof(int):
                case true when value.GetType() == typeof(string):
                    {
                        Console.WriteLine($"{value.GetType().Name}:{value}");
                        break;
                    }
            }

            return Result;
        }
---
Int32:123
Int16:777
String:aiueo


解決済み
[ 親 103641 / □ Tree ] 返信 編集キー/


管理者用

- Child Tree -