如何针对UsedRange剪辑ExcelReference?

我正在使用Excel-DNA在Excel中开发一些UDF。 从Excel传递到我的UDF的一个参数是一个范围。 使用特定范围时,UDF可正常工作,如“A1:C50”。 以下是我的函数定义的示例:

[ExcelCommand()] public static object CalcSMA(object[,] range, int num_points) { ... } 

但是,如果传递整个列范围(例如“A:C”),则会出现“内存不足”错误。 我可以通过设置参数属性AllowReference = true来避免错误,并将参数types更改为对象,如下例所示:

 [ExcelCommand()] public static object CalcSMA([ExcelArgument("Range", AllowReference=true)]object range, int num_points) { ExcelReference xref = (ExcelReference)range; ... } 

但是现在我想知道UDF实际需要多less行。 我可以尝试迭代工作表中的所有行,但效率非常低。 有没有办法将ExcelReference(xref)剪辑到使用的范围? 我想避免使函数volatile(IsMacroType = true),但会这样做,如果需要的话。

在VBA(或COM)中,可以将Range参数与Range参数的Parent的UsedRange相交。 但是在XLL中,获取使用的范围并不简单,因为XLL接口没有为工作表提供UsedRange方法。 所以你必须使用COM接口(这在XLL UDF中是有问题的)。 我构build了一个使用AfterCalculate事件来caching每个工作表使用范围的例程。

这里有一些讨论这样做的方法https://fastexcel.wordpress.com/2014/09/26/getting-used-range-in-an-xll-udf-multi-threading-and-com/

请注意,如果您愿意使您的UDF成为单线程macros型UDF,则可以使用GETDOCUMENT(10)XLL api。 但是痛苦可能不值得。

根据Charles和Govert的build议,我最终实现了以下内容:

 public class UsedRangeCache { protected static Dictionary<IntPtr, ExcelReference> _usedRanges = new Dictionary<IntPtr, ExcelReference>(); protected static Application _app; /// <summary> /// Call this method when the XLL is initialized /// </summary> public static void Initialize(Application app) { _app = app; for (int i = 0; i < app.Workbooks.Count; i++ ) { app_WorkbookOpen(app.Workbooks[i + 1]); } app.WorkbookOpen += app_WorkbookOpen; app.WorkbookBeforeClose += app_WorkbookBeforeClose; app.AfterCalculate += app_AfterCalculate; } // Refresh references static void app_AfterCalculate() { for (int i = 0; i < _app.Workbooks.Count; i++) { UpdateCache(_app.Workbooks[i + 1]); } } // Remove references static void app_WorkbookBeforeClose(Workbook book, ref bool Cancel) { for (int i = 0; i < book.Worksheets.Count; i++) { Worksheet sheet = book.Worksheets[i + 1] as Worksheet; if (sheet != null) { ExcelReference xref = (ExcelReference)XlCall.Excel(XlCall.xlSheetId, sheet.Name); if (_usedRanges.ContainsKey(xref.SheetId)) { _usedRanges.Remove(xref.SheetId); } } } } // Create references static void app_WorkbookOpen(Workbook book) { UpdateCache(book); } // Update cache private static void UpdateCache(Workbook book) { for (int i = 0; i < book.Worksheets.Count; i++) { Worksheet sheet = book.Worksheets[i + 1] as Worksheet; if (sheet != null) { ExcelReference xref = (ExcelReference)XlCall.Excel(XlCall.xlSheetId, sheet.Name); ExcelReference xused = new ExcelReference( sheet.UsedRange.Row, sheet.UsedRange.Row + sheet.UsedRange.Rows.Count, sheet.UsedRange.Column, sheet.UsedRange.Column + sheet.UsedRange.Columns.Count, xref.SheetId); if (_usedRanges.ContainsKey(xref.SheetId)) { _usedRanges.Remove(xref.SheetId); } _usedRanges.Add(xref.SheetId, xused); } } } /// <summary> /// Get used range /// </summary> public static ExcelReference GetUsedRange(ExcelReference xref) { ExcelReference ret = null; _usedRanges.TryGetValue(xref.SheetId, out ret); return ret; } }