C# 如何在不循环的情况下将数组(Range.Value)传递给本机.NET类型?
我试图在VBA中使用本机C 35;C# 如何在不循环的情况下将数组(Range.Value)传递给本机.NET类型?,c#,excel,vba,arraylist,com,C#,Excel,Vba,Arraylist,Com,我试图在VBA中使用本机C 35;ArrayList上的后期绑定来填充using方法,但我不知道如何将另一个ArrayList以外的对象作为参数传递给它。。。到目前为止我试过的其他方法都失败了。。。 所以基本上我现在所做的(注意:list是通过mscorlib.dll的C#的ArrayList) 但是,如果例如i可以=500K,则这是非常低效和丑陋的 在VBA中,这也适用于: ArrayList1.AddRange ArrayList2 但我真正需要/想要的是传递一个数组,而不是Array
ArrayList
上的后期绑定来填充using方法,但我不知道如何将另一个ArrayList
以外的对象作为参数传递给它。。。到目前为止我试过的其他方法都失败了。。。
所以基本上我现在所做的(注意:
list
是通过mscorlib.dll的C#的ArrayList
)
但是,如果例如i
可以=500K
,则这是非常低效和丑陋的
在VBA中,这也适用于:
ArrayList1.AddRange ArrayList2
但我真正需要/想要的是传递一个数组,而不是ArrayList2
因此,我在一个小型C#控制台应用程序中测试了它,它似乎工作得很好。下面的代码在纯C#控制台应用程序中运行良好
ArrayList list = new ArrayList();
string[] strArr = new string[1];
strArr[0] = "hello";
list.AddRange(strArr);
因此,返回到我的VBA模块尝试执行相同的操作时失败了
Dim arr(1) As Variant
arr(0) = "WHY!?"
Dim arrr As Variant
arrr = Range("A1:A5").Value
list.AddRange arr ' Fail
list.AddRange arrr ' Fail
list.AddRange Range("A1:A5").Value ' Fail
注意:我尝试传递变体和Collection
,Ranges的本机VBA数组-除了另一个ArrayList
之外的所有内容都失败了
如何将本机VBA数组作为参数传递给ArrayList
或任何从范围创建集合而不循环的替代方法
附加问题:*或者可能还有另一个内置的.Net COM可见集合,可以从VBA范围对象或数组填充,而不需要循环,并且已经有了.Reverse
注意:我知道我可以制作一个.dll包装器来实现这一点,但我对本机解决方案感兴趣-如果有的话
更新
为了更好地说明为什么我想要完全避免显式迭代,这里有一个例子(为了简单起见,它只使用一列)
请注意:我必须循环的唯一时间是填充列表(ArrayList)。我想做的只是找到一种方法,将arr2
加载到ArrayList或其他与.NET兼容的类型中,而不使用循环
在这一点上,我发现没有本机/内置的方法可以做到这一点,这就是为什么我想尝试实现我自己的方法,如果可行的话,可能会提交一个Interop库的更新。下面是一个概念证明,作为@phoog评论的扩展。正如他所指出的,这种方法需要一段时间
实施ICollection
在VBA IDE中,添加对mscorlib.tlb的引用:Tools-->References,然后浏览以查找.NET Framework mscorlib.tlb。我的地址是“C:\Windows\Microsoft.NET\Framework\vX.X.XXXXX\mscorlib.tlb”
创建一个名为“clsWrapLongArray”的新类,如下所示:
Option Compare Database
Option Explicit
Implements ICollection
Dim m_lngArr() As Long
Public Sub LoadArray(lngArr() As Long)
m_lngArr = lngArr
End Sub
Private Sub ICollection_CopyTo(ByVal arr As mscorlib.Array, ByVal index As Long)
Dim i As Long
Dim j As Long
j = LBound(m_lngArr)
For i = index To index + (ICollection_Count - 1)
arr.SetValue m_lngArr(j), i
j = j + 1
Next
End Sub
Private Property Get ICollection_Count() As Long
ICollection_Count = UBound(m_lngArr) - LBound(m_lngArr) + 1
End Property
Private Property Get ICollection_IsSynchronized() As Boolean
'Never called for this example, so I'm leaving it blank
End Property
Private Property Get ICollection_SyncRoot() As Variant
'Never called for this example, so I'm leaving it blank
End Property
创建一个名为“mdlMain”的新模块来运行示例:
Option Compare Database
Option Explicit
Public Sub Main()
Dim arr(0 To 3) As Long
arr(0) = 1
arr(1) = 2
arr(2) = 3
arr(3) = 4
Dim ArrList As ArrayList
Set ArrList = New ArrayList
Dim wrap As clsWrapLongArray
Set wrap = New clsWrapLongArray
wrap.LoadArray arr
ArrList.AddRange wrap
End Sub
如果在结束子节点上放置断点并运行Main()
,则可以通过检查即时窗口中的ArrList
来查看它包含从长数组添加的4个值。您还可以单步查看代码,查看ArrayList
实际上调用了ICollection接口成员Count
和CopyTo
,以实现这一点
我可以看到,可以扩展这个想法,创建工厂函数来包装Excel范围、VBA数组、集合等类型
同样的方法也适用于字符串数组!:D
我通过谷歌搜索“System.ArrayList”“SAFEARRAY”
和“System.Array”“SAFEARRAY”
找到了一些非常接近的答案,并使用“COM封送”进行了细化,因为VBA数组是COM安全数组,但是没有什么能很好地解释如何将它们转换为在.NET调用中使用。这是低效的,因为.Value2
是一个调用。多次调用它会增加很多互操作开销。将项添加到循环中的数组应该快得多:
Dim list as Object
Set list = CreateObject("System.Collections.ArrayList")
Dim i As Long
Dim a as Variant
a = Range("A1:A5").Value2
For i = 1 To 5
list.Add a(i,1)
Next
如何将本机VBA数组作为参数传递给ArrayList
我不认为您可以-本机VBA数组不是ICollection
,因此创建一个数组的唯一方法是在循环中复制值
它在C#console应用程序中工作的原因是C#中的数组是ICollection
s,因此AddRange
方法可以很好地接受它们
list.AddRange Range("A1:A5").Value
范围的值被封送为数组。当然,这是你能想象到的最基本的.NET类型。然而,这一个是有钟的,它不是一个“普通的”.NET数组。VBA是一个运行时环境,它喜欢创建第一个元素从索引1开始的数组。这是.NET中的非一致数组类型,CLR喜欢第一个元素从索引0开始的数组。唯一可以用于这些的.NET类型是System.Array类
另一个复杂的情况是,该数组是二维数组。这使得您试图将它们转换为ArrayList时感到尴尬,因为多维数组没有枚举器
因此,这段代码工作得很好:
public void AddRange(object arg) {
var arr = (Array)arg;
for (int ix = ar.GetLowerBound(0); ix <= arr2.GetUpperBound(0); ++ix) {
Debug.Print(arr.GetValue(ix, 1).ToString());
}
}
public void AddRange(对象arg){
var arr=(数组)arg;
对于(int ix=ar.GetLowerBound(0);ix,AddRange的参数必须实现ICollection。VBA数组没有(互操作层显然也没有对此进行模拟)。为什么不一步将范围中的数据加载到数组中(就像您现在所做的那样),然后循环该数组?因为它在内存中(缓慢的一步是查询range.Value)它对性能的影响应该很小。我看到了关于.Reverse
-您可以描述一个特定的用例吗?我可以想象一个特殊用途的原生VBA数据结构可以提供令人满意的性能,假设这就是您希望使用.NET库的原因。@Blackhawk忽略.Reverse操作实际上,我最初认为,如果我能以ArrayList
的形式(作为一个单列或多维数组)将Range.Value
传递给.Net,我就可以很容易地使用已经实现的方法,而反向只是一个例子。重点是在VBA中基于Range收集数组(在上)
list.AddRange Range("A1:A5").Value
public void AddRange(object arg) {
var arr = (Array)arg;
for (int ix = ar.GetLowerBound(0); ix <= arr2.GetUpperBound(0); ++ix) {
Debug.Print(arr.GetValue(ix, 1).ToString());
}
}
class Vba1DRange : IEnumerable<double> {
private Array arr;
public Vba1DRange(object vba) {
arr = (Array)vba;
}
public double this[int index] {
get { return Convert.ToDouble(arr.GetValue(index + 1, 1)); }
set { arr.SetValue(value, index + 1, 1); }
}
public int Length { get { return arr.GetUpperBound(0); } }
public IEnumerator<double> GetEnumerator() {
int upper = Length;
for (int index = 0; index < upper; ++index)
yield return this[index];
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return GetEnumerator();
}
public void AddRange(object arg) {
var arr = new Vba1DRange(arg);
foreach (double elem in arr) {
Debug.Print(elem.ToString());
}
// or:
for (int ix = 0; ix < arr.Length; ++ix) {
Debug.Print(arr[ix].ToString());
}
// or:
var list = new List<double>(arr);
}