如何通过一个数组(Range.Value)到本机.NET类型不循环? [英] How to pass an array (Range.Value) to a native .NET type without looping?

查看:136
本文介绍了如何通过一个数组(Range.Value)到本机.NET类型不循环?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我所试图做的是一个填充的 的ArrayList 使用的 .AddRange() VBA中使用的方法在本机C#后期绑定的ArrayList ,但我无法弄清楚如何比其他类型的目标的ArrayList 传递给它作为参数......别的我曾尝试迄今失败...



所以基本上我现在正在做的(注:列表是C#的的ArrayList 通过mscorlib.dll中)

 昏暗的列表作为对象
设置列表=的CreateObject(System.Collections.ArrayList)昏暗我只要
对于i = 1到5
    list.Add范围(A&放大器; I).Value2
下一个

但是,这是非常低效的和丑陋的,如果例如 I 可以是 = 500K

在VBA这也可以工作:

  ArrayList1.AddRange ArrayList2

但我真的需要/想是传递一个数组,而不是 ArrayList2


所以<一个href=\"http://dotnetfluent.com/reference/csharp-reference/collections-generics/arraylist/addrange/using-addrange-to-add-an-array-into-an-arraylist-in-c/\"相对=nofollow>我听说我可以传递一个数组.NET中的 .AddRange()参数。我在一个小的C#控制台应用程序对其进行了测试它似乎工作得很好。下面的工作在一个纯粹的C#控制台应用程序就好了。

  ArrayList的列表=新的ArrayList();
字符串[] = strArr新的字符串[1];
strArr [0] =你好;
list.AddRange(strArr);

所以要回到我的VBA模块试图做的相同的失败..

 暗淡ARR(1)变
ARR(0)=为什么!?昏暗arrr为Variant
arrr =范围(A1:A5)。价值list.AddRange ARR'失败
list.AddRange arrr'失败
list.AddRange范围(A1:A5)值'失败。

请注意:我曾尝试过变量和收藏土生土长的VBA阵列,范围 - 除了其他一切的ArrayList 失败了。

我如何通过一个本地VBA数组作为参数传递给了的ArrayList

或者创建范围从集合不用循环??任何替代

<子>奖金的问题:*或许还有另外一个内置的.Net COM可见集合,可以从VBA 范围对象或数组来填充不用循环和已经有一个 .Reverse

注:我知道我可以做一个.dll封装来实现这一点,但我感兴趣的是原生解决方案 - 如果存在任何


更新

为了更好地说明我为什么要完全避免显式迭代 - 这里是一个例子(只使用一列为了简单起见的)

 的Sub Main()    初始化.NET的ArrayList中
    昏暗的列表作为对象
    设置列表=的CreateObject(System.Collections.ArrayList)
    有两种方法来填充列表。
    我选择这一项,因为它是比上一小套环更有效
    有关详细信息,请参阅:http://www.dotnetperls.com/array-optimization
    list.Add范围(A1)
    list.Add范围(A2)
    list.Add范围(A3)
    list.Add范围(A4)
    list.Add范围(A5),这是只有五值确定,但不与50K。    另类的方式来填充列表
    根据上面的链接这种方法有一个糟糕的表现
    我暗淡,只要
    暗淡ARR2为Variant
    ARR2 =范围(A1:A5)。值2
    对于i = 1到5
    'list.Add ARR2(I,1)
    '下一个    呼叫本地ArrayList.Reverse
    注:无循环需要!
    list.Reverse    '获取从列表中数组
    昏暗的ARR为Variant
    ARR = list.ToArray    打印逆转即时窗口
    Debug.Print加入(ARR,CHR(10))    打印反转列表为s preadsheet开始B1
    范围(B1)。调整(UBound函数(ARR)+ 1,1)= Application.Transpose(ARR)结束小组

请注意:我有循环的唯一时间是填充列表(ArrayList的)我会喜欢做的将只是找到一种方法来加载 ARR2 成一个ArrayList或其他.NET兼容的类型,而循环。

在这一点上我看到,有没有本地/内置的方式来做到这一点,这就是为什么我想我会尝试实现我自己的路,也许如果它的作品了提交的更新的互操作库。


解决方案

  list.AddRange范围(A1:A5)。价值

该范围的值将被编组为的阵列的。这是对你的想象,当然最基本的.NET类型。这一个然而具有铃上,它不是一个正常的.NET阵列。 VBA是喜欢创建数组的第一个元素开始于索引1这是一个的不符合标准的.NET中的数组类型,CLR喜欢阵列,其第一个元素开始索引为0的唯一一个运行环境您可以使用这些.NET类型是System.Array类。

这是额外的复杂性在于,阵列是二维的阵列。这使胡说你试图让他们转换到一个ArrayList,多维数组没有一个枚举。

所以这code工作的很好:

 公共无效的AddRange(ARG对象){
        VAR ARR =(阵列)ARG;
        为(中间体IX = ar.GetLowerBound(0);九&下; = arr2.GetUpperBound(0); ++ⅸ){
            Debug.Print(arr.GetValue(九,1)的ToString());
        }
    }

您可能不关心太多。你可以使用一个包装尴尬阵列和的作用的像一个矢量一个小访问器类:

 类Vba1DRange:IEnumerable的&LT;双&GT; {
    私人阵列ARR;
    公共Vba1DRange(VBA对象){
        ARR =(阵列)VBA;
    }
    公共双本[INT指数] {
        {返回Convert.ToDouble(arr.GetValue(索引+ 1,1)); }
        集合{arr.SetValue(值,指数+1,1); }
    }
    公众诠释长度{{返回arr.GetUpperBound(0); }}
    公众的IEnumerator&LT;双&GT;的GetEnumerator(){
        INT上=长度;
        对于(INT指数= 0;指数 - LT;上++指数)
            产量返回此[指数]
    }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator(){
        返回的GetEnumerator();
    }

现在你可以把它写了自然的方式:

 公共无效的AddRange(ARG对象){
        VAR ARR =新Vba1DRange(ARG);
        的foreach(ARR中的双ELEM){
            Debug.Print(elem.ToString());
        }
        // 要么:
        对于(INT九= 0;九&LT; arr.Length ++九){
            Debug.Print(ARR [九]的ToString());
        }
        // 要么:
        VAR名单=新名单,LT;双&GT;(ARR);
    }

What I am trying to do is populate an ArrayList using .AddRange() method in VBA using late binding on native C# ArrayList, but I can't figure out how to pass an object other than another ArrayList to it as argument... anything else I have tried so far fails...

So basically what I am doing now (Note: list is C#'s ArrayList via mscorlib.dll)

Dim list as Object
Set list = CreateObject("System.Collections.ArrayList")

Dim i As Long
For i = 1 To 5
    list.Add Range("A" & i).Value2
Next

But this is quite inefficient and ugly if for example i can be = 500K.

In VBA this also works:

ArrayList1.AddRange ArrayList2

But what I really need/would like is to pass an array instead of ArrayList2


So I heard I can pass an array to the .AddRange() parameter in .NET. I tested it in a small C# console application and it seemed to work just fine. The below works just fine in a pure C# console application.

ArrayList list = new ArrayList();
string[] strArr = new string[1];
strArr[0] = "hello";
list.AddRange(strArr);

So going back to my VBA module trying to do the same it fails..

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

Note: I have tried passing a native VBA Array of Variants and Collection, Ranges - everything except another ArrayList failed.

How do I pass a native VBA array as a parameter to an ArrayList?

Or any alternative for creating a collection from Range without looping??

Bonus question: *Or maybe there is another built-in .Net COM Visible Collection that can be populated from VBA Range Object or Array without looping and that already has a .Reverse?

NOTE: I am aware that I can make a .dll wrapper to achieve this but I am interested in native solutions - if any exist.


Update

To better illustrate why I want to completely avoid explicit iteration - here's an example (it uses only one column for simplicity)

Sub Main()

    ' Initialize the .NET's ArrayList
    Dim list As Object
    Set list = CreateObject("System.Collections.ArrayList")


    ' There are two ways to populate the list.
    ' I selected this one as it's more efficient than a loop on a small set
    ' For details, see: http://www.dotnetperls.com/array-optimization
    list.Add Range("A1")
    list.Add Range("A2")
    list.Add Range("A3")
    list.Add Range("A4")
    list.Add Range("A5") ' It's OK with only five values but not with 50K.

    ' Alternative way to populate the list
    ' According to the above link this method has a worse performance
    'Dim i As Long
    'Dim arr2 As Variant
    'arr2 = Range("A1:A5").Value2
    'For i = 1 To 5
    '    list.Add arr2(i, 1)
    'Next

    ' Call native ArrayList.Reverse
    ' Note: no looping required!
    list.Reverse

    ' Get an array from the list
    Dim arr As Variant
    arr = list.ToArray

    ' Print reversed to Immediate Window
    'Debug.Print Join(arr, Chr(10))

    ' Print reversed list to spreadsheet starting at B1
    Range("B1").Resize(UBound(arr) + 1, 1) = Application.Transpose(arr)

End Sub

Please notice: the only time I have to loop is to populate the list (ArrayList) what I would love to do would be just to find a way to load the arr2 into an ArrayList or another .NET compatible type without loops.

At this point I see that there is no native/built-in way to do so that's why I think I am going to try to implement my own way and maybe if it works out submit an update for the Interop library.

解决方案

   list.AddRange Range("A1:A5").Value 

The range's Value gets marshaled as an array. That's about the most basic .NET type you can imagine of course. This one however has bells on, it is not a "normal" .NET array. VBA is a runtime environment that likes to create arrays whose first element starts at index 1. That's a non-conformant array type in .NET, the CLR likes arrays whose first element starts at index 0. The only .NET type you can use for those is the System.Array class.

An extra complication is that the array is a two-dimensional array. That puts the kibosh on your attempts to get them converted to an ArrayList, multi-dimensional arrays don't have an enumerator.

So this code works just fine:

    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());
        } 
    }

You probably don't care for that too much. You could use a little accessor class that wraps the awkward Array and acts like a vector:

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();
    }

Now you can write it the "natural" way:

    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);
    }

这篇关于如何通过一个数组(Range.Value)到本机.NET类型不循环?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆