Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/excel/29.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何在不循环的情况下将数组(Range.Value)传递给本机.NET类型?_C#_Excel_Vba_Arraylist_Com - Fatal编程技术网

C# 如何在不循环的情况下将数组(Range.Value)传递给本机.NET类型?

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

我试图在VBA中使用本机C 35;
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);
    }