web-dev-qa-db-ja.com

ライブ(保存されていない)ExcelデータとC#オブジェクト間のインターフェイスの最速の方法

開いているExcelワークブックとc#オブジェクトとの間でデータを読み書きする最速の方法を知りたいです。その背景は、Excelから使用し、Excelに保持されているデータを使用するc#アプリケーションを開発したいということです。

ビジネスロジックはc#アプリケーションにありますが、データはExcelワークブックにあります。ユーザーはExcelを使用し、Excelブックのボタンをクリック(または同様の操作)してc#アプリケーションを開始します。次に、c#アプリケーションはExcelブックからデータを読み取り、データを処理してから、Excelブックにデータを書き戻します。
Excelブックに読み込んで書き戻す必要のあるデータのブロックが多数ある場合がありますが、通常は比較的小さいサイズ、たとえば10行20列になります。場合によっては、50,000行40列のオーダーの大量のデータリストを処理する必要があります。

これはVSTOを使用して言うのは比較的簡単ですが、最速の(ただし、堅牢でエレガントな)ソリューションが何であるかを知り、速度を把握したいと思います。ソリューションがサードパーティ製品の使用を推奨しているのか、C++を使用しているのかは気にしません。

明らかな解決策はVSTOまたは相互運用機能を使用することですが、現在データの読み取りに使用しているVBAと比較して、パフォーマンスがどのようなものか、または他の解決策があるかどうかはわかりません。

これは、VSTOがVBAよりも劇的に遅いと専門家交換に投稿されましたが、それは数年前であり、パフォーマンスが向上したかどうかはわかりません。

http://www.experts-exchange.com/Microsoft/Development/VSTO/Q_23635459.html

ありがとう。

30
jw_pr

C#アプリケーションがスタンドアロンアプリケーションの場合、言語をC#からC++に切り替えることで実行できる最適化を圧倒する、クロスプロセスマーシャリングが常に含まれます。この状況では、C#のように聞こえる最も好ましい言語に固執します。

ただし、withinExcel内で実行されるアドインを作成する場合は、操作でクロスプロセス呼び出しが回避され、約50倍高速に実行されます。

Excel内でアドインとして実行する場合、VBAは最速のオプションの1つですが、それでもCOMが含まれるため、XLLアドインを使用したC++呼び出しが最速になります。ただし、VBAは、Excelオブジェクトモデルの呼び出しに関してはまだかなり高速です。ただし、実際の計算速度に関しては、VBAは完全にコンパイルされたコードではなくpcodeとして実行されるため、ネイティブコードよりも約2〜3倍遅く実行されます。これは非常に悪いように聞こえますが、一般的なExcelアドインまたはアプリケーションでかかる実行時間の大部分がExcelオブジェクトモデルの呼び出しに関係しているためではありません。したがって、VBAと完全にコンパイルされたCOMアドインは、ネイティブにコンパイルされたVB 6.0は、約5〜15%遅くなるだけで、目立ちません。

VB 6.0はコンパイルされたCOMアプローチであり、Excel関連以外の呼び出しではVBAより2〜3倍高速に実行されますが、VB 6.0は現時点では約12年前であり、64では実行されません。ビットモード、たとえば、32ビットまたは64ビットを実行するようにインストールできるOffice 2010をインストールする場合。64ビットExcelの使用量は現時点ではわずかですが、使用量が増えるため、VB 6.0このため。

C#、Excelアドインとしてインプロセスで実行すると、Excelオブジェクトモデルへの呼び出しがVBAと同じ速さで実行され、Excel以外の呼び出しはVBAよりも2〜3倍速く実行されます。ただし、Microsoftが推奨するアプローチは、たとえば COM Shim Wizard を使用して、完全にシムを実行することです。シムされることにより、Excelはコードから保護され(障害がある場合)、コードは、問題を引き起こす可能性のある他のサードパーティのアドインから完全に保護されます。ただし、これの欠点は、シムソリューションが別のAppDomain内で実行されることです。これには、約40倍の実行速度ペナルティが発生するクロスAppDomainマーシャリングが必要です。これは、多くのコンテキストで非常に顕著です。

Visual Studio Tools for Office(VSTO)を使用するアドインは、シム内に自動的に読み込まれ、別のAppDomain内で実行されます。 VSTOを使用する場合、これを回避することはできません。したがって、Excelオブジェクトモデルを呼び出すと、実行速度が約40倍低下します。 VSTOは非常に豊富なExcelアドインを作成するためのゴージャスなシステムですが、実行速度はあなたのようなアプリケーションにとっての弱点です。

ExcelDnaは、C#コードを使用できる無料のオープンソースプロジェクトであり、C#コードを使用するXLLアドインに変換されます。つまり、ExcelDnaはC#コードを解析し、必要なC++コードを作成します。自分で使ったことはありませんが、プロセスに精通していてとても印象的です。 ExcelDnaは、それを使用する人々から非常に良いレビューを得ています。 [編集:以下のGovertのコメントに従って、次の修正に注意してください。「こんにちはマイク-Excel-Dnaの実装を明確にするために小さな修正を追加したい:すべての管理対象からExcelへの接着剤はリフレクションを使用したマネージアセンブリからのランタイム-追加のコンパイル前の手順やC++コード生成はありません。また、Excel-Dnaは.NETを使用しますが、Excelと通信するときにCOMの相互運用は必要ありません-.xllとしてネイティブインターフェイスは.NETから直接使用できます(ただし、必要に応じてCOMを使用することもできます)。これにより、高性能のUDFとマクロが可能になります。」 – govert]

Add-inExpressもご覧ください。これは無料ではありませんが、C#でコーディングできるようになり、ソリューションを別のAppDomainに変換できますが、実行速度は非常に優れていると思います。実行速度を正しく理解している場合、Add-in Expressがこれをどのように実行するかはわかりませんが、FastPathAppDomainマーシャリングと呼ばれるものを利用している可能性があります。ただし、私はAdd-in Expressにあまり詳しくないので、これについては引用しないでください。あなたはそれをチェックして、あなた自身の研究をするべきです。 [編集:Charles Williamsの回答を読むと、Add-inExpressがCOMとCAPIの両方のアクセスを有効にしているようです。また、Govertは、ExcelDNAによってCOMとより高速なCAPIアクセスの両方が可能になると述べています。したがって、両方をチェックして、ExcelDnaと比較することをお勧めします。]

私のアドバイスは、アドインExpressとExcelDnaを調査することです。どちらのアプローチでも、最もよく知っていると思われるC#を使用してコーディングできます。

他の主な問題は、どのように電話をかけるかです。たとえば、Excelは、やり取りされるデータの全範囲を配列として処理する場合に非常に高速です。これは、セルを個別にループするよりもはるかに効率的です。たとえば、次のコードは、Excel.Range.set_Valueアクセサメソッドを使用して、10 x10の値の配列を10x10の範囲のセルに1回のショットで割り当てます。

void AssignArrayToRange()
{
    // Create the array.
    object[,] myArray = new object[10, 10];

    // Initialize the array.
    for (int i = 0; i < myArray.GetLength(0); i++)
    {
        for (int j = 0; j < myArray.GetLength(1); j++)
        {
            myArray[i, j] = i + j;
        }
    }

    // Create a Range of the correct size:
    int rows = myArray.GetLength(0);
    int columns = myArray.GetLength(1);
    Excel.Range range = myWorksheet.get_Range("A1", Type.Missing);
    range = range.get_Resize(rows, columns);

    // Assign the Array to the Range in one shot:
    range.set_Value(Type.Missing, myArray);
}

同様に、Excel.Range.get_Valueアクセサメソッドを使用して、1つのステップで範囲から値の配列を読み取ることができます。これを実行してから配列内の値をループする方が、範囲のセル内の値を個別にループするよりもはるかに高速です。

38
Mike Rosenblum

これを課題として取り上げ、ExcelとC#の間でデータをシャッフルする最も速い方法は、Excel-Dna -- http://exceldna.codeplex.com を使用することです。 (免責事項:私はExcel-Dnaを開発しています。しかし、それはまだ真実です...)

ネイティブの.xllインターフェイスを使用するため、VSTOまたは別のCOMベースのアドインアプローチで発生するであろうすべてのCOM統合オーバーヘッドをスキップします。 Excel-Dnaを使用すると、メニューまたはリボンボタンに接続されたマクロを作成して、範囲を読み取り、処理して、Excelの範囲に書き戻すことができます。すべてC#のネイティブExcelインターフェイスを使用しています。COMオブジェクトは見えません。

現在の選択を配列に取り込み、配列内のすべての数値を2乗し、セルA1から開始して結果をシート2に書き込む小さなテスト関数を作成しました。 http://exceldna.codeplex.com からダウンロードできる(無料の)Excel-Dnaランタイムを追加する必要があります。

私はC#を読み込み、処理して1秒以内に100万セルの範囲をExcelに書き戻します。これはあなたにとって十分に速いですか?

私の関数は次のようになります。

using ExcelDna.Integration;
public static class RangeTools {

[ExcelCommand(MenuName="Range Tools", MenuText="Square Selection")]
public static void SquareRange()
{
    object[,] result;

    // Get a reference to the current selection
    ExcelReference selection = (ExcelReference)XlCall.Excel(XlCall.xlfSelection);
    // Get the value of the selection
    object selectionContent = selection.GetValue();
    if (selectionContent is object[,])
    {
        object[,] values = (object[,])selectionContent;
        int rows = values.GetLength(0);
        int cols = values.GetLength(1);
        result = new object[rows,cols];

        // Process the values
        for (int i = 0; i < rows; i++)
        {
            for (int j = 0; j < cols; j++)
            {
                if (values[i,j] is double)
                {
                    double val = (double)values[i,j];
                    result[i,j] = val * val;
                }
                else
                {
                    result[i,j] = values[i,j];
                }
            }
        }
    }
    else if (selectionContent is double)
    {
        double value = (double)selectionContent;
        result = new object[,] {{value * value}}; 
    }
    else
    {
        result = new object[,] {{"Selection was not a range or a number, but " + selectionContent.ToString()}};
    }

    // Now create the target reference that will refer to Sheet 2, getting a reference that contains the SheetId first
    ExcelReference sheet2 = (ExcelReference)XlCall.Excel(XlCall.xlSheetId, "Sheet2"); // Throws exception if no Sheet2 exists
    // ... then creating the reference with the right size as new ExcelReference(RowFirst, RowLast, ColFirst, ColLast, SheetId)
    int resultRows = result.GetLength(0);
    int resultCols = result.GetLength(1);
    ExcelReference target = new ExcelReference(0, resultRows-1, 0, resultCols-1, sheet2.SheetId);
    // Finally setting the result into the target range.
    target.SetValue(result);
}
}
40
Govert

配列の使用に関するMikeRosenblumのコメントに加えて、私はまさにアプローチ(VSTO +配列)を使用しており、それを測定したとき、実際の読み取り速度自体はミリ秒以内であったことを付け加えたいと思います。読み取り/書き込みの前にイベント処理と画面更新を無効にすることを忘れないでください。操作の完了後に再度有効にすることを忘れないでください。

C#を使用すると、ExcelVBA自体とまったく同じように1ベースの配列を作成できます。これは非常に便利です。特に、VSTOでも、Excel.Rangeオブジェクトから配列を抽出する場合、配列は1ベースであるため、Excel指向の配列を1ベースに保つと、常にチェックする必要がなくなります。配列は1ベースまたは0ベースです。 (配列内の列の位置が重要である場合、0ベースおよび1ベースの配列を処理する必要があるのは非常に困難です)

通常、Excel.Rangeを配列に読み込むと、次のようになります。

var myArray = (object[,])range.Value2;


Mike Rosenblumのarray-writeのバリエーションでは、次のような1ベースの配列を使用しています。

int[] lowerBounds = new int[]{ 1, 1 };
int[] lengths = new int[] { rowCount, columnCount };  
var myArray = 
    (object[,])Array.CreateInstance(typeof(object), lengths, lowerBounds);

var dataRange = GetRangeFromMySources();

// this example is a bit too atomic; you probably want to disable 
// screen updates and events a bit higher up in the call stack...
dataRange.Application.ScreenUpdating = false;
dataRange.Application.EnableEvents = false;

dataRange = dataRange.get_Resize(rowCount, columnCount);
dataRange.set_Value(Excel.XlRangeValueDataType.xlRangeValueDefault, myArray);

dataRange.Application.ScreenUpdating = true;
dataRange.Application.EnableEvents = true;
4
code4life

Excelデータへの最速のインターフェースはCAPIです。このインターフェイスを使用して.NETをExcelにリンクする製品は数多くあります。

私がこれを行うのが好きな2つの製品は、Excel DNA(無料でオープンソース)とAddin Express(商用製品であり、C APIとCOMインターフェイスの両方が利用可能)です。

3

まず、ソリューションをExcel UDF(ユーザー定義関数)にすることはできません。マニュアルでは、次の定義を示しています。「Excel UDFは、エンドユーザーが数式で使用できるようにExcelでカスタム関数を作成するために使用されます。」より良い定義を提案してもかまいません:)

この定義は、UDFがUIにボタンを追加したり(XLLがCommandBar UIを変更できることを知っています)、キーボードショートカットやExcelイベントを傍受したりできないことを示しています。

つまり、ExcelDNAはXLLアドインの開発を目的としているため、範囲外です。 XLLアドインとExcelAutomationアドインの開発が可能になるため、同じことがAdd-inExpressのExcelを対象とした機能にも当てはまります。

Excelイベントを処理する必要があるため、ソリューションはスタンドアロンアプリケーションにすることができますが、そのようなアプローチには明らかな制限があります。唯一の実際の方法は、COMアドインを作成することです。これにより、Excelイベントを処理し、ExcelUIにカスタムのものを追加できます。 3つの可能性があります。

  • VSTO
  • アドインエクスプレス(COMアドイン機能)
  • 共有アドイン(VSの[新しいプロジェクト]ダイアログの対応する項目を参照)

Excel COMアドインの開発について言えば、上記の3つのツールは、ビジュアルデザイナー、シミングなどのさまざまな機能を提供します。ただし、Excelオブジェクトモデルへのアクセス速度に違いはないと思います。たとえば、デフォルトのAppDomainからCOMオブジェクトを取得することが、別のAppDomainから同じCOMオブジェクトを取得するのと異なる理由がわかりません(想像もできません)。ところで、共有アドインを作成し、COMシムWizardを使用してシムすることで、シミングが動作速度に影響を与えるかどうかを確認できます。

スピードII。昨日書いたように、「セルの範囲の読み取りと書き込みを高速化する最善の方法は、その範囲を参照するExcel.Range型の変数を作成してから、Valueプロパティとの間で配列の読み取り/書き込みを行うことです。変数の。」しかし、フランチェスコの言うことに反して、私はこれをVSTOに帰するものではありません。これはExcelオブジェクトモデルの機能です。

スピードIII。最速のExcelUDFは、.NET言語ではなく、ネイティブC++で記述されています。 ExcelDNAとAdd-inExpressによって生成されたXLLアドインの速度を比較していません。ここでは実質的な違いはないと思います。

総括する。私はあなたが間違った方向にいると確信しています:アドインエクスプレス、VSTOまたは共有アドインに基づくCOMアドインは、Excelセルを同じ速度で読み書きする必要があります。誰かがこの声明を反証するならば、私は(誠実に)うれしいです。

今あなたの他の質問について。 VSTOでは、Office2000-2010をサポートするCOMアドインの開発は許可されていません。 Office 2003-2010を完全にサポートするには、3つの異なるコードベースと少なくとも2つのバージョンのVisualStudioが必要です。 Excel 2003用のVSTOベースのアドインを展開するには、強い神経と幸運の一部が必要です。Add-inExpressを使用すると、単一のコードベースですべてのOfficeバージョンのCOMアドインを作成できます。 Add-in Expressは、Excel 2000-2010(32ビットおよび64ビット)にアドインをインストールする準備ができているセットアッププロジェクトを提供します。 ClickOnceデプロイメントも搭載されています。

VSTOは、1つの領域でアドインエクスプレスに勝っています。いわゆるドキュメントレベルのアドインを作成できます。 .NETコードが背後にあるワークブックまたはテンプレートを想像してみてください。しかし、そのようなものの展開が悪夢であるならば、私は驚かないでしょう。

Excelイベント。すべてのExcelイベントはMSDNに一覧表示されます。たとえば、 Excel 2007イベント を参照してください。

ベラルーシ(GMT + 2)からよろしく、

AndreiSmolinアドインエクスプレスチームリーダー

私はVBAコード(マクロ)を使用してデータを収集および圧縮し、C#への1回の呼び出しでこのデータを取得しました。その逆も同様です。これはおそらく最もパフォーマンスの高いアプローチです。

C#を使用すると、常にマーシャリングを使用する必要があります。 VSTOまたはCOM相互運用機能を使用すると、基盤となる通信レイヤー(マーシャリングオーバーヘッド)は同じです。

VBA(Visual Basic For Application)では、Excelのオブジェクトを直接操作します。したがって、このデータへのアクセスは常に高速になります。

しかし.... C#でデータを取得すると、このデータの操作がはるかに高速になります。

VB6またはC++を使用している場合は、COMインターフェイスも使用し、クロスプロセスマーシャリングにも直面します。

そのため、クロスプロセス呼び出しとマーシャリングを最小限に抑える方法を探しています。

0
GvS