I need to call a C++ routine with a KeyValuePair parameter. I have an example in C#;
KeyValuePair[] dataArray =
new KeyValuePair[]{
new KeyValuePair() { key = 1234, value = "Atlanta" },
new KeyValuePair() { key = 70, value = "Atlanta" },
new KeyValuePair() { key = 2320, value = "999999999" }};
This seems to translates to;
Dim dataArray As KeyValuePair() = New KeyValuePair() {New KeyValuePair() With {
.key = 1234,
.value = "Atlanta"
}, New KeyValuePair() With {
.key = 70,
.value = "Atlanta"
}}
in VB.
I have two questions;
What is this structure called? I would call it an array of KeyValuePairs. So I know how to reference the structure when I search.
How does one add additional values dynamically?
EDIT:
More specifically I have a function whose purpose is to build the same type of structure statically built in the C# code above by reading values from a database. I had originally written the code assuming the "list of" key value pairs is what I needed. Here is that code;
Private Function buildDataRecord(ByRef objRecord As OracleDataReader) As List(Of KeyValuePair(Of Integer, String))
Dim i As Integer = 0
Dim lstFieldData As List(Of KeyValuePair(Of Integer, String)) = New List(Of KeyValuePair(Of Integer, String))
Dim strFieldName As String
On Error GoTo buildDataRecordError
For i = 0 To objRecord.FieldCount - 1
strFieldName = objRecord.GetName(i)
If strFieldName.Substring(0, 3) = "IN_" _
Then
lstFieldData.Add(New KeyValuePair(Of Integer, String)(CInt(strFieldName.Substring(3)), Trim(objRecord(i).ToString)))
End If
Next
Return lstFieldData
buildDataRecordError:
Err.Raise(Err.Number,
"buildDataRecord",
Err.Description,
Err.HelpFile,
Err.HelpContext)
When calling C++ I get the error;
Cannot marshal 'parameter #4': Generic types cannot be marshaled.
My assumption is I do not have the correct data type.
What is this structure called?
An Array of KeyValuePair. Arrays are CLR types shared by all languages.
How does one add additional values dynamically?
An array is a fixed-size data structure, so you don't just add additional items like you can with a List. But you can resize an array. VB has ReDim, or you can use the generic Array.Resize method that could also be used from C#.
C++ code rely a lot about how your memory is organized for your type.
C# generic type are just describing what the type should contains but nothing about memory organisation. Memory organisation is decided by JIT when it knows the true types of T1 and T2. JIT will probably align Item1 and Item2 on multiple of 8 in memory. It can store Item2 before Item1 (for example if item1 is byte and item2 an int). And in Debug the organisation can be different than in Release. This is why generic are not supported.
You have to define your own struct type for all not generic type versions of KeyValuePair you want to use. And I recommend you to use attribute like this to make thing explicit. Example if you want to handle KeyValuePair<byte, int>
[StructLayout(LayoutKind.Explicit)]
public struct KeyValuePairOfIntAndByte // For KeyValuePair<int, byte>
{
// note how fields are inversed in memory...
// this is the kind of optimisation JIT usually does behind your back
[FieldOffset(0)] public int Item2;
[FieldOffset(sizeof(int))] public byte Item1;
}
So make conversion of your KeyValuePair<int, byte> instance to an array of this struct and that should be ok.
Note that you will have to pin memory while your C++ code is using this C# array too... otherwise Garbage Collector can decide at any time to move your array elsewhere in memory (while your C++ code is using it...)
I strongly recommend you to read this: https://learn.microsoft.com/en-us/dotnet/framework/interop/copying-and-pinning if you want to continue on this way
For information Swig is a library that generate C# code for you that wrap C++ library so you can use C++ library as if it was written in C# (a lot simpler then...) without thinking too much about all the work you have to do. If you look at the generated code you will understand how complex it can be to do interop code right.
Related
I decompiled some C# 7 libraries and saw ValueTuple generics being used. What are ValueTuples and why not Tuple instead?
https://learn.microsoft.com/en-gb/dotnet/api/system.tuple
https://learn.microsoft.com/en-gb/dotnet/api/system.valuetuple
What are ValueTuples and why not Tuple instead?
A ValueTuple is a struct which reflects a tuple, same as the original System.Tuple class.
The main difference between Tuple and ValueTuple are:
System.ValueTuple is a value type (struct), while System.Tuple is a reference type (class). This is meaningful when talking about allocations and GC pressure.
System.ValueTuple isn't only a struct, it's a mutable one, and one has to be careful when using them as such. Think what happens when a class holds a System.ValueTuple as a field.
System.ValueTuple exposes its items via fields instead of properties.
Until C# 7, using tuples wasn't very convenient. Their field names are Item1, Item2, etc, and the language hadn't supplied syntax sugar for them like most other languages do (Python, Scala).
When the .NET language design team decided to incorporate tuples and add syntax sugar to them at the language level an important factor was performance. With ValueTuple being a value type, you can avoid GC pressure when using them because (as an implementation detail) they'll be allocated on the stack.
Additionally, a struct gets automatic (shallow) equality semantics by the runtime, where a class doesn't. Although the design team made sure there will be an even more optimized equality for tuples, hence implemented a custom equality for it.
Here is a paragraph from the design notes of Tuples:
Struct or Class:
As mentioned, I propose to make tuple types structs rather than
classes, so that no allocation penalty is associated with them. They
should be as lightweight as possible.
Arguably, structs can end up being more costly, because assignment
copies a bigger value. So if they are assigned a lot more than they
are created, then structs would be a bad choice.
In their very motivation, though, tuples are ephemeral. You would use
them when the parts are more important than the whole. So the common
pattern would be to construct, return and immediately deconstruct
them. In this situation structs are clearly preferable.
Structs also have a number of other benefits, which will become
obvious in the following.
Examples:
You can easily see that working with System.Tuple becomes ambiguous very quickly. For example, say we have a method which calculates a sum and a count of a List<Int>:
public Tuple<int, int> DoStuff(IEnumerable<int> values)
{
var sum = 0;
var count = 0;
foreach (var value in values) { sum += value; count++; }
return new Tuple(sum, count);
}
On the receiving end, we end up with:
Tuple<int, int> result = DoStuff(Enumerable.Range(0, 10));
// What is Item1 and what is Item2?
// Which one is the sum and which is the count?
Console.WriteLine(result.Item1);
Console.WriteLine(result.Item2);
The way you can deconstruct value tuples into named arguments is the real power of the feature:
public (int sum, int count) DoStuff(IEnumerable<int> values)
{
var res = (sum: 0, count: 0);
foreach (var value in values) { res.sum += value; res.count++; }
return res;
}
And on the receiving end:
var result = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {result.sum}, Count: {result.count}");
Or:
var (sum, count) = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {sum}, Count: {count}");
Compiler goodies:
If we look under the cover of our previous example, we can see exactly how the compiler is interpreting ValueTuple when we ask it to deconstruct:
[return: TupleElementNames(new string[] {
"sum",
"count"
})]
public ValueTuple<int, int> DoStuff(IEnumerable<int> values)
{
ValueTuple<int, int> result;
result..ctor(0, 0);
foreach (int current in values)
{
result.Item1 += current;
result.Item2++;
}
return result;
}
public void Foo()
{
ValueTuple<int, int> expr_0E = this.DoStuff(Enumerable.Range(0, 10));
int item = expr_0E.Item1;
int arg_1A_0 = expr_0E.Item2;
}
Internally, the compiled code utilizes Item1 and Item2, but all of this is abstracted away from us since we work with a decomposed tuple. A tuple with named arguments gets annotated with the TupleElementNamesAttribute. If we use a single fresh variable instead of decomposing, we get:
public void Foo()
{
ValueTuple<int, int> valueTuple = this.DoStuff(Enumerable.Range(0, 10));
Console.WriteLine(string.Format("Sum: {0}, Count: {1})", valueTuple.Item1, valueTuple.Item2));
}
Note that the compiler still has to make some magic happen (via the attribute) when we debug our application, as it would be odd to see Item1, Item2.
The difference between Tuple and ValueTuple is that Tuple is a reference type and ValueTuple is a value type. The latter is desirable because changes to the language in C# 7 have tuples being used much more frequently, but allocating a new object on the heap for every tuple is a performance concern, particularly when it's unnecessary.
However, in C# 7, the idea is that you never have to explicitly use either type because of the syntax sugar being added for tuple use. For example, in C# 6, if you wanted to use a tuple to return a value, you would have to do the following:
public Tuple<string, int> GetValues()
{
// ...
return new Tuple(stringVal, intVal);
}
var value = GetValues();
string s = value.Item1;
However, in C# 7, you can use this:
public (string, int) GetValues()
{
// ...
return (stringVal, intVal);
}
var value = GetValues();
string s = value.Item1;
You can even go a step further and give the values names:
public (string S, int I) GetValues()
{
// ...
return (stringVal, intVal);
}
var value = GetValues();
string s = value.S;
... Or deconstruct the tuple entirely:
public (string S, int I) GetValues()
{
// ...
return (stringVal, intVal);
}
var (S, I) = GetValues();
string s = S;
Tuples weren't often used in C# pre-7 because they were cumbersome and verbose, and only really used in cases where building a data class/struct for just a single instance of work would be more trouble than it was worth. But in C# 7, tuples have language-level support now, so using them is much cleaner and more useful.
I looked at the source for both Tuple and ValueTuple. The difference is that Tuple is a class and ValueTuple is a struct that implements IEquatable.
That means that Tuple == Tuple will return false if they are not the same instance, but ValueTuple == ValueTuple will return true if they are of the same type and Equals returns true for each of the values they contain.
In addition to the comments above, one unfortunate gotcha of ValueTuple is that, as a value type, the named arguments get erased when compiled to IL, so they're not available for serialisation at runtime.
i.e. Your sweet named arguments will still end up as "Item1", "Item2", etc. when serialised via e.g. Json.NET.
Other answers forgot to mention important points.Instead of rephrasing, I'm gonna reference the XML documentation from source code:
The ValueTuple types (from arity 0 to 8) comprise the runtime implementation that underlies
tuples in C# and struct tuples in F#.
Aside from created via language syntax, they are most easily created via the
ValueTuple.Create factory methods.
The System.ValueTuple types differ from the System.Tuple types in that:
they are structs rather than classes,
they are mutable rather than readonly, and
their members (such as Item1, Item2, etc) are fields rather than properties.
With introduction of this type and C# 7.0 compiler, you can easily write
(int, string) idAndName = (1, "John");
And return two values from a method:
private (int, string) GetIdAndName()
{
//.....
return (id, name);
}
Contrary to System.Tuple you can update its members (Mutable) because they are public read-write Fields that can be given meaningful names:
(int id, string name) idAndName = (1, "John");
idAndName.name = "New Name";
Late-joining to add a quick clarification on these two factoids:
they are structs rather than classes
they are mutable rather than readonly
One would think that changing value-tuples en-masse would be straightforward:
foreach (var x in listOfValueTuples) { x.Foo = 103; } // wont even compile because x is a value (struct) not a variable
var d = listOfValueTuples[0].Foo;
Someone might try to workaround this like so:
// initially *.Foo = 10 for all items
listOfValueTuples.Select(x => x.Foo = 103);
var d = listOfValueTuples[0].Foo; // 'd' should be 103 right? wrong! it is '10'
The reason for this quirky behavior is that the value-tuples are exactly value-based (structs) and thus the .Select(...) call works on cloned-structs rather than on the originals. To resolve this we must resort to:
// initially *.Foo = 10 for all items
listOfValueTuples = listOfValueTuples
.Select(x => {
x.Foo = 103;
return x;
})
.ToList();
var d = listOfValueTuples[0].Foo; // 'd' is now 103 indeed
Alternatively of course one might try the straightforward approach:
for (var i = 0; i < listOfValueTuples.Length; i++) {
listOfValueTuples[i].Foo = 103; //this works just fine
// another alternative approach:
//
// var x = listOfValueTuples[i];
// x.Foo = 103;
// listOfValueTuples[i] = x; //<-- vital for this alternative approach to work if you omit this changes wont be saved to the original list
}
var d = listOfValueTuples[0].Foo; // 'd' is now 103 indeed
Hope this helps someone struggling to make heads of tails out of list-hosted value-tuples.
I have two sets of dictionaries that each contain the same keys and have initialized values.
Using unsafe code, I would like to swap their addresses:
Dictionary<string, List<object>> d1 = ...
Dictionary<string, List<object>> d2 = ...
unsafe void SwapEntries(string index)
{
int* tmp = &d1[index];
&d1[index] = &d2[index]
&d2[index] = tmp;
}
Assuming I've recalled my pointer arithmetic properly, the output I'm looking for would be this:
d1 = new Dictionary<string, List<int>>() { "a", { 1, 2, 3 } };
d2 = new Dictionary<string, List<int>>() { "a", { 4, 5, 6 } };
SwapEntries("a");
Console.WriteLine(d1["a"]); //4,5,6
Console.WriteLine(d2["a"]); //1,2,3
However, when I try to write this, I get the compile error "Cannot take the address of the given expression."
1) Is there a faster way of performing the address swap that I've missed? Performance is the only priority here.
2) Is my pointer arithmetic correct?
3) Do I need to move to a wrapper or a different data structure entirely in order to be able to perform the address swap as described?
I agree with Martin Ullrich's answer.
The expression d1[index] is not a variable. It is an invocation of the get accessor of the indexer defined by Dictionary<,>. You cannot take a pointer to that with the & operator.
Besides, in this case, the type of it is List<object>. You can only take pointers to value types, and List<> is a class type.
Even if you did have the true storage location, and it was of type object[], it would still be impossible since the element type of the array is object. So arr[0] (corresponding to d1[index][0]) would be a class type again, and you cannot take the address of that.
Scott Chamberlain's comment to your question gives an easy approach. Just use
void SwapEntries(string index)
{
var tmp = d1[index];
d1[index] = d2[index];
d2[index] = tmp;
}
This just involves passing around references to the two existing List<object> instances in question.
Automatic pointers to dictionary members aren't supported - they only work for Arrays or data types that use C# 7's "ref return" feature for indexers, properties or methods.
If you wanted to actually take the ref addresses of the two locations, there is now an option for it
CollectionsMarshal.GetValueRefOrNullRef(d1, "a")
So if you had a Swap function which accepted pointers:
void Swap<T>(ref T a, ref T b)
{
var tmp = a;
a = b;
b = tmp;
}
You could call it like this
Swap(ref CollectionsMarshal.GetValueRefOrNullRef(d1, "x"),
ref CollectionsMarshal.GetValueRefOrNullRef(d2, "x"));
shaplab
The benefit of this over just using normal dictionary indexers is that you only look up each location once, rather than once for get and once for set.
I'm currently migrating some VB.Net code to C#, and am quite confused about the struct in VB.NET.
Here is the code from VB which I would like to ask the equivalent in C# or some explanation.
Below is the struct format
struct Pt_Appt
{
public Pt_Appt(int pID, int aID)
{
PatientID = pID;
AppointID = aID;
}
public int PatientID;
public int AppointID;
}
Below is the code that is confusing me, I've tried and it seems creating array of struct but why negative value.
Dim pt_appts(-1) As pt_appt
It declares an empty array of type pt_appt. The upper bound is set to -1. Since the lower bound is always 0, this results in no valid indices. Arrays are re-sized via ReDim. However, this should be avoided if possible. In most cases, a List(Of T) or similar structures are more appropriate.
The C# equivalent is
Pt_Appt[] pt_appts = new Pt_Appt[0];
My background is all in dynamic languages for many years, so reasoning about how static typed languages get data from JSON where the key/values can be different types is becoming an issue that really has me stuck. Here's an example of the data I have:
{
"par": 4,
"level": [0,1,0,1,1, 0,1,0,1,1, 0,1,0,1,1, 0,1,0,1,1, 0,1,0,1,1]
}
And I want to get this into some local variables like these
int par;
List<bool> levelData;
I'm using MiniJSON.cs to do the parsing, and it gives me back Dictionary<string, object> but I can't figure out how to get the par and level values from that and convert them to the different types they actually represent.
The problem with trying to convert to List<int> was that MiniJSON parses integers into Int64 and int is 32-bit, so the conversion won't be allowed implicitly because of data loss. Converting to a list of Int64 worked!
List<Int64> cells = (List<Int64>)data["level"];
You have to provide a "decoder" that transforms the dictionary into a typed data structure. This is especially obvious since you are using 0 and 1 to represent boolean values.
Suppose the class SomeClass holds your typed data. The decoder would have to do something like this:
SomeClass decode(Dictionary<string,object> d) {
var m = new SomeClass();
m.par = (int)d["par"];
m.levelData = ((List)d["level"]).Select(x => (int)x == 0 ? false : true).ToList();
return m;
}
So, basically you must turn the (untyped) objects into typed values, and you have to do that explicitly. Note that here it is assumed that your list of zeroes and ones is homogeneous (i.e. does not contain "strange" values like strings, which would be perfectly legal in a dynamically typed language).
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);
}
Here's a proof of concept as an expansion of #phoog's comment. As he points out, the AddRange method takes an ICollection.
Implementing ICollection
In the VBA IDE, add a reference to mscorlib.tlb: Tools--->References, then browse to find your .NET Framework mscorlib.tlb. Mine was at "C:\Windows\Microsoft.NET\Framework\vX.X.XXXXX\mscorlib.tlb".
Create a new class called "clsWrapLongArray" as follows:
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
Here is the Array.SetValue method used.
Create a new module called "mdlMain" to run the example:
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
If you put a breakpoint on the End Sub and run Main(), you can see by inspecting ArrList in the immediate window that it contains the 4 values added from the Long array. You can also step through the code to see that ArrayList actually calls the ICollection interface members Count and CopyTo to make it happen.
I could see that it might be possible to expand on this idea to create Factory functions to wrap types like Excel Ranges, VBA Arrays, Collections, etc.
This same method works with String arrays! :D
I found some very close answers by Googling "System.ArrayList" "SAFEARRAY" and "System.Array" "SAFEARRAY" and refining with "COM Marshaling", since VBA arrays are COM SAFEARRAYs, but nothing quite explained how one might convert them for use in .NET calls.
It's inefficient because .Value2 is a COM call. Calling it thousands of times adds a lot of interop overhead. Adding items to an array in a loop should be MUCH faster:
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
How do I pass a native VBA array as a parameter to an ArrayList?
I don't think you can - a native VBA array is not an ICollection, so the only way to create one would be to copy the values in a loop.
The reason it works in a C# console application is because arrays in C# are ICollections, so the AddRange method accepts them just fine.