Can a variant array be passed to C# using com interop? - c#

I try to transfer some data between Excel and C#. For this I wrote a simple C# class with a property to set and get the data.
[Guid("xxx")]
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public class Vector
{
private object[,] _values;
public object[,] ExcelValues
{
get { ... }
set { ... }
}
}
Getting the ExcelValues property in VBA works well, but it is not possible to set it in VBA. The VBA code does not compile if I try to set the property:
Dim values As Variant
With myRange
' typo: Set values = Range(.Offset(0, 0), .Offset(100, 0))
values = Range(.Offset(0, 0), .Offset(100, 0))
End With
Dim data As New Vector
' this doesn't compile
data.ExcelValues = values
' this works
values = data.ExcelValues
Any suggestions how I can acomplish this, without setting each value from the variant Array one at a time?

I found a solution based on code that was posted here. A variant array has to be passed from VBA to C# as an object (not object[,]). Then it can be converted to something that is more handy by using reflection:
[Guid("xxx")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Vector
{
[ComVisible(false)]
public IList<double> Values { get; set; }
public object[,] GetExcelValues()
{
// own extension method
return Values.ConvertToExcelColumn();
}
public void SetExcelValues(object comArray)
{
IEnumerable<object> values = ConvertExcelCloumnToEnumerable(comArray);
Values = new List<double>(values.Select(Convert.ToDouble));
}
private static IEnumerable<object> ConvertExcelCloumnToEnumerable(object comObject)
{
Type comObjectType = comObject.GetType();
if (comObjectType != typeof(object[,]))
return new object[0];
int count = (int)comObjectType.InvokeMember("Length", BindingFlags.GetProperty, null, comObject, null);
var result = new List<object>(count);
var indexArgs = new object[2];
for (int i = 1; i <= count; i++)
{
indexArgs[0] = i;
indexArgs[1] = 1;
object valueAtIndex = comObjectType.InvokeMember("GetValue", BindingFlags.InvokeMethod, null, comObject, indexArgs);
result.Add(valueAtIndex);
}
return result;
}
}
The other way - from C# to VBA - it can be passed more comfortable as object[,] or double[,].
Hope there are no syntax typos :).

By using Set you're telling VBA that "values" is an object (in this case a Range), but then you're not using set when assigning that to ExcelValues. Try dropping the Set when you're reading the values.
With myRange
values = Range(.Offset(0, 0), .Offset(100, 0)).Value
End With

Related

How to write 2D data containing excel errors using c# VSTO

I have Excel functions where I am writing 2D data using VSTO. In some cases, I need to write an Excel error like #N/A. How do I do this? I have attempted to use XlCVError.xlErrNA but it just writes the value 2042.
Codewise, I have the following:
public void WriteChanges(int startRow, int startCol, int endRow, int endCol, int rows, int columns, IItems items)
{
object[,] data = new object[rows, columns];
var startCell = (Range)this.sheet.Cells[startRow, startCol];
var endCell = (Range)this.sheet.Cells[endRow, endCol];
var rangeToWrite = this.sheet.Range[startCell, endCell];
var error = new ExcelError();
foreach (var item in items)
{
int row = item.Row;
int col = item.Column;
if (!(item.Value is eExcelErrror))
{
data[row, col] = item.Value;
}
else
{
data[row, col] = this.error.ToVstoError((eExcelError)item.Value);
}
}
rangeToWrite.Value = data;
}
public enum eExcelError
{
ExcelErrorValue = 2015,
ExcelErrorNA = 2042
}
public class ExcelError
{
public object ToVstoNativeError(eExcelError value)
{
switch (value)
{
case eExcelError.ExcelErrorNA:
return this.VstoNoData();
case eExcelError.ExcelErrorValue:
default:
return this.VstoErrorValue();
}
}
public object VstoNoData()
{
return XlCVError.xlErrNA;
}
public object VstoErrorValue()
{
return XlCVError.xlErrValue;
}
}
I don't think it is possible to set Excel errors into a cell... which kind of makes sense. If you were the user and you saw a #DIV/0 error in a cell, but there was no formula, or you saw a #NAME error and you inserted the defined name into the names manager and it still didn't work, that would be very confusing.
What I would recommend doing is cell.Value = "#REF". At least this way the user can see that this is just a string and not an actual error from Excel that they may fall into a rabbit hole chasing.
Reference: https://learn.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel.range.errors?view=excel-pia#Microsoft_Office_Interop_Excel_Range_Errors (note: get only)

C# Array of references to Variables and or Form objects

What I am trying to do is to have an array of references to variables. By which I mean the equivalent to a C array of pointers to ints (for example).
Example: (!!not real code!!)
int a = 4;
int b = 5;
int c = 6;
List<ref int> tmp = new List<ref int>();
tmp.Add(ref a);
tmp.Add(ref b);
tmp.Add(ref c);
tmp[0] = 16;
tmp[1] = 3;
tmp[2] = 1000;
Console.Writeline(tmp[0] + " " + a); // 16 16
Console.Writeline(tmp[1] + " " + b); // 3 3
Console.Writeline(tmp[2] + " " + c); // 1000 1000
The specifics of my case: I have a list of strings that will correspond to the keys in a dictionary. What I think I want to have is a list of Tuples where Type1 is a reference to either an int or string, and Type2 is a reference to an Textbox.
I will be iterating through this list, using the string to get the value from the dictionary (and doing stuff with that data) then storing the results of that into Type1. And eventually I will be taking the data from those Type1 variable references and copying their data to the corresponding Type2 Textbox.
That's the gist of what I think I want to do. If someone thinks that my approach is overly complicated, I will say that I need to keep the Textboxes as they are sadly, so I can't just make an array and iterate through it. And it would be perferable to keep the Type1 variables seperate too, though not quite as necessary.
Now, from reading around, I thought Func<> looked like it was the closest thing to being useful for what I want, so I tried to use the following (with Type1, as an object because it needs to handle both ints and strings)
List<Tuple<string, Func<object>, Func<object>>>
but I was unsure how to use that to get references to the variables.
What you're specifically asking for isn't possible; what would be more appropriate (and has the convenience of actually working!) would be to design a class structure around what you're trying to do.
For instance, something like this:
public class MyObject // Name it appropriately
{
public object Value { get; set; }
public string Key { get; set; }
public TextBox TextBox { get; set; }
}
Then, in your code, you can do something akin to this...
Dictionary<string, object> values = ...
List<MyObject> objects = ...
foreach(var item in objects)
{
item.Value = values[item.Key];
// process your data
item.TextBox = item.Value.ToString();
}
Obviously, this is just a rough design and the class here serves as little more than a data transfer container. You could make the class "smarter" by, for example, using the setter for the Value property to set the value of the TextBox automatically. But this should hopefully give you the general idea of how something like this would be done in an OO fashion.
EDIT Here's how your example would look.
MyObject a = new MyObject() { Value = 4 };
MyObject b = new MyObject() { Value = 5 };
MyObject c = new MyObject() { Value = 6 };
List<MyObject> tmp = new List<MyObject>();
tmp.Add(a);
tmp.Add(b);
tmp.Add(c);
tmp[0].Value = 16;
tmp[1].Value = 3;
tmp[2].Value = 1000;
Console.Writeline(tmp[0].Value.ToString() + " " + a.Value.ToString()); // 16 16
Console.Writeline(tmp[1].Value.ToString() + " " + b.Value.ToString()); // 3 3
Console.Writeline(tmp[2].Value.ToString() + " " + c.Value.ToString()); // 1000 1000
You can't store references using C#. You can only use the ref keyword when calling a method.
You can use pointers, but you can only do that with a fixed expression and within an unsafe context.
It is possible to fake this kind of thing using delegates, but I'm not sure if it's what you're looking for. I'm also fairly sure that you really need to redesign your approach, but nevertheless, here's an example of how you can fake it...
Firstly, write a "value wrapper" class like so:
public class ValueWrapper<T>
{
readonly Func<T> get;
readonly Action<T> set;
public ValueWrapper(Func<T> get, Action<T> set)
{
this.get = get;
this.set = set;
}
public T Value
{
get
{
return get();
}
set
{
set(value);
}
}
}
Then you can use that to change values:
void run()
{
int x = 0;
var intWrapper = new ValueWrapper<int>(() => x, value => x = value);
test(intWrapper);
Console.WriteLine(x); // Prints 42, which shows that x was changed.
TextBox textBox = new TextBox {Text = ""};
var stringWrapper = new ValueWrapper<string>(() => textBox.Text, value => textBox.Text = value);
test(stringWrapper);
Console.WriteLine(textBox.Text); // Prints "Changed".
}
static void test(ValueWrapper<int> wrapper)
{
wrapper.Value = 42;
}
static void test(ValueWrapper<string> wrapper)
{
wrapper.Value = "Changed";
}
You can also create a wrapper in one method and pass it to a different method which uses the wrapper to change a property in the original wrapped object, like so:
void run()
{
TextBox textBox = new TextBox {Text = ""};
var wrapper = test1(textBox);
test2(wrapper);
Console.WriteLine(textBox.Text); // Prints "Changed"
}
void test2(ValueWrapper<string> wrapper)
{
wrapper.Value = "Changed";
}
ValueWrapper<string> test1(TextBox textBox)
{
return new ValueWrapper<string>(() => textBox.Text, value => textBox.Text = value);
}
Warning: This does lead to some fairly head-scratching code, for example:
void run()
{
var intWrapper = test();
intWrapper.Value = 42;
Console.WriteLine(intWrapper.Value); // Works, but where is the value? It can't be the x inside test()!
}
ValueWrapper<int> test()
{
int x = 0;
var intWrapper = new ValueWrapper<int>(() => x, value => x = value);
return intWrapper;
}
So we returned a ValueWrapper from test() which is apparently wrapping a local variable from inside test(). And then we can apparently change the value and print it out...
This isn't really what's happening, of course, but it can be quite confusing!
you can use pointers in this case, use unsafe keyword for method and set project unsafe to allow pointers in c#, also you can encapsulate the value in a class and in C# each class is of reference type
i used this and works perfect:
exp.
public int value1 = 3;
public int value2 = 4;
public int value3 = 5;
public void Method1()
{
int[] values = { value1, value2, value3};
for (int i = 0; i < values.Length; i ++)
{
Console.WriteLine(values[i]);
}
}

Calling COM in C# from VBScript with Array

Calling a COM function written in C# from VBScript is OK in first example with strings.
public bool IsEqualTo(string firstString, string SecondString)
{
bool areEqual = String.Equals(firstString, SecondString, StringComparison.Ordinal);
if (areEqual){
return true;
}
else return false;
}
VBScript: MsgBox oTestCom.IsEqualTo(one,one)
Unsure how to pass and manipulate arrays in second example. Is it best to use Array, ArrayList or Object?
public Array SortAscending (Array firstArray)
{
firstArray.Sort;
return firstArray;
}
VBScript: arrout = oTestCom.SortAscending("car","plane","boat")
In C#, use object as the array parameter type. In VBScript, construct the array using the Array function. Here is an example:
C# code:
using System;
using System.Linq;
using System.Runtime.InteropServices;
namespace MySampleComX
{
[ComVisible(true)]
public class Class1
{
public void SortIntArray(ref object array)
{
if (array.GetType() != typeof(object[]))
{
throw new ArgumentException("Argument must be an array of integers");
}
array = ((object[]) array).OrderBy(Convert.ToInt32).ToArray();
}
}
}
VBScript code:
Set obj = CreateObject("MySampleComX.Class1")
arr = Array(3, 1, 2)
obj.SortIntArray arr
MsgBox Join(arr) ' Outputs "1 2 3"
please try this, I try and can be work
C# code
public object[] TestAray(ref object array)
{
if (array.GetType() != typeof(object[]))
{
throw new ArgumentException("Argument must be an array of integers");
}
var StrArray = ((object[])array).Cast<string>().ToArray();
string[] res = StrArray;
for (int i = 0; i < res.Length; i++)
{
string tids;
tids = Convert.ToString(res[i]);
}
}
VBScript code
set obj=createObject("JustLib.TP_RateSchedules")
dim a
a=Array("5","10","15","20")
obj.TestAray(a)

Excel ExcelDNA C# / Try to copy Bloomberg BDH() behavior (writing Array after a web request)

I want to copy Bloomberg BDH behavior.
BDH makes a web request and write an array (but doesn't return an array style). During this web request, the function returns "#N/A Requesting".
When the web request finished, the BDH() function writes the array result in the worksheet.
For example, in ExcelDNA, I succeed to write in the worksheet with a thread.
The result if you use the code below in a DNA file, the result of
=WriteArray(2;2)
will be
Line 1 > #N/A Requesting Data (0,1)
Line 2 > (1,0) (1,1)
The last issue is to replace #N/A Requesting Data with the value and copy the formula.
When you uncomment //xlActiveCellType.InvokeMember("FormulaR1C1Local", you are near the result but you don't have the right behavior
File .dna
<DnaLibrary Language="CS" RuntimeVersion="v4.0">
<![CDATA[
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using ExcelDna.Integration;
public static class WriteForXL
{
public static object[,] MakeArray(int rows, int columns)
{
if (rows == 0 && columns == 0)
{
rows = 1;
columns = 1;
}
object[,] result = new string[rows, columns];
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
result[i, j] = string.Format("({0},{1})", i, j);
}
}
return result;
}
public static object WriteArray(int rows, int columns)
{
if (ExcelDnaUtil.IsInFunctionWizard())
return "Waiting for click on wizard ok button to calculate.";
object[,] result = MakeArray(rows, columns);
var xlApp = ExcelDnaUtil.Application;
Type xlAppType = xlApp.GetType();
object caller = xlAppType.InvokeMember("ActiveCell", BindingFlags.GetProperty, null, xlApp, null);
object formula = xlAppType.InvokeMember("FormulaR1C1Local", BindingFlags.GetProperty, null, caller, null);
ObjectForThread q = new ObjectForThread() { xlRef = caller, value = result, FormulaR1C1Local = formula };
Thread t = new Thread(WriteFromThread);
t.Start(q);
return "#N/A Requesting Data";
}
private static void WriteFromThread(Object o)
{
ObjectForThread q = (ObjectForThread) o;
Type xlActiveCellType = q.xlRef.GetType();
try
{
for (int i = 0; i < q.value.GetLength(0); i++)
{
for (int j = 0; j < q.value.GetLength(1); j++)
{
if (i == 0 && j == 0)
continue;
Object cellBelow = xlActiveCellType.InvokeMember("Offset", BindingFlags.GetProperty, null, q.xlRef, new object[] { i, j });
xlActiveCellType.InvokeMember("Value", BindingFlags.SetProperty, null, cellBelow, new[] { Type.Missing, q.value[i, j] });
}
}
}
catch(Exception e)
{
}
finally
{
//xlActiveCellType.InvokeMember("Value", BindingFlags.SetProperty, null, q.xlRef, new[] { Type.Missing, q.value[0, 0] });
//xlActiveCellType.InvokeMember("FormulaR1C1Local", BindingFlags.SetProperty, null, q.xlRef, new [] { q.FormulaR1C1Local });
}
}
public class ObjectForThread
{
public object xlRef { get; set; }
public object[,] value { get; set; }
public object FormulaR1C1Local { get; set; }
}
}
]]>
</DnaLibrary>
#To Govert
BDH has become a standard in finance industry. People do not know how to manipulate an array (even the Ctrl+Shift+Enter).
BDH is the function that made Bloomberg so popular (to the disadvantage of Reuters).
However I will think of using your method or RTD.
Thanks for all your work in Excel DNA
I presume you have tried the Excel-DNA ArrayResizer sample, which carefully avoids many of the issue you are running into. I'd like to understand what you see as the disadvantages of the array-formula-writing approach.
Now, about your function:
Firstly, you can't safely pass the 'caller' Range COM object to another thread - rather pass a string with the address, and get the COM object from the other thread (using a call to ExcelDnaUtil.Application on the worker thread). Most of the time you'll get lucky, though.
The better way to do this is from the worker thread to get Excel to run a macro on the main thread - by calling Application.Run. The Excel-DNA ArrayResizer sample shows how this can be done.
Secondly, you almost certainly don't want the ActiveCell, but rather Application.Caller. The ActiveCell might well have nothing to do with the cell where the formula is running from.
Next - Excel will recalculate your function every time you set the Formula again - hence putting you in an endless loop when you enable the Formula set in your finally clause. You cannot set both the Value and the Formula for a cell - if a cell has a Formula then Excel will use the formula to calculate the Value. If you set the Value, the Formula gets removed.
It's not clear what you want to actually leave in the [0,0] cell - IIRC Bloomberg modifies the formula there in a way that makes it remember how large a range was written to. You could try to add some parameters to your function that tell your function whether to recalculate or whether to return an actual value as its result.
Finally, you might want to reconsider whether the Bloomberg BDH function is a good example for what you want to do. It breaks the dependency calculation of your sheet, which has implications both for performance and for maintaining consistency of the spreadsheet model.
My issue was :
writing dynamic array
data are retrieved asynchronous via a webservice
After discussing with Govert, I chose to take a result as an array and not to copy Bloomberg functions (write an array but return a single value).
Finally, to solve my issue, I used http://excel-dna.net/2011/01/30/resizing-excel-udf-result-arrays/
and reshape the resize() function.
This code is not RTD.
The code belows works in a .dna file
<DnaLibrary RuntimeVersion="v4.0" Language="C#">
<![CDATA[
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.ComponentModel;
using ExcelDna.Integration;
public static class ResizeTest
{
public static object[,] MakeArray(int rows, int columns)
{
object[,] result = new string[rows, columns];
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
result[i,j] = string.Format("({0},{1})", i, j);
}
}
return result;
}
public static object MakeArrayAndResize()
{
// Call Resize via Excel - so if the Resize add-in is not part of this code, it should still work.
return XlCall.Excel(XlCall.xlUDF, "Resize", null);
}
}
public class Resizer
{
static Queue<ExcelReference> ResizeJobs = new Queue<ExcelReference>();
static Dictionary<string, object> JobIsDone = new Dictionary<string, object>();
// This function will run in the UDF context.
// Needs extra protection to allow multithreaded use.
public static object Resize(object args)
{
ExcelReference caller = XlCall.Excel(XlCall.xlfCaller) as ExcelReference;
if (caller == null)
return ExcelError.ExcelErrorNA;
if (!JobIsDone.ContainsKey(GetHashcode(caller)))
{
BackgroundWorker(caller);
return ExcelError.ExcelErrorNA;
}
else
{
// Size is already OK - just return result
object[,] array = (object[,])JobIsDone[GetHashcode(caller)];
JobIsDone.Remove(GetHashcode(caller));
return array;
}
}
/// <summary>
/// Simulate WebServiceRequest
/// </summary>
/// <param name="caller"></param>
/// <param name="rows"></param>
/// <param name="columns"></param>
static void BackgroundWorker(ExcelReference caller)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (sender, args) =>
{
Thread.Sleep(3000);
};
bw.RunWorkerCompleted += (sender, args) =>
{
// La requete
Random r = new Random();
object[,] array = ResizeTest.MakeArray(r.Next(10), r.Next(10));
JobIsDone[GetHashcode(caller)] = array;
int rows = array.GetLength(0);
int columns = array.GetLength(1);
EnqueueResize(caller, rows, columns);
AsyncRunMacro("DoResizing");
};
bw.RunWorkerAsync();
}
static string GetHashcode(ExcelReference caller)
{
return caller.SheetId + ":L" + caller.RowFirst + "C" + caller.ColumnFirst;
}
static void EnqueueResize(ExcelReference caller, int rows, int columns)
{
ExcelReference target = new ExcelReference(caller.RowFirst, caller.RowFirst + rows - 1, caller.ColumnFirst, caller.ColumnFirst + columns - 1, caller.SheetId);
ResizeJobs.Enqueue(target);
}
public static void DoResizing()
{
while (ResizeJobs.Count > 0)
{
DoResize(ResizeJobs.Dequeue());
}
}
static void DoResize(ExcelReference target)
{
try
{
// Get the current state for reset later
XlCall.Excel(XlCall.xlcEcho, false);
// Get the formula in the first cell of the target
string formula = (string)XlCall.Excel(XlCall.xlfGetCell, 41, target);
ExcelReference firstCell = new ExcelReference(target.RowFirst, target.RowFirst, target.ColumnFirst, target.ColumnFirst, target.SheetId);
bool isFormulaArray = (bool)XlCall.Excel(XlCall.xlfGetCell, 49, target);
if (isFormulaArray)
{
object oldSelectionOnActiveSheet = XlCall.Excel(XlCall.xlfSelection);
object oldActiveCell = XlCall.Excel(XlCall.xlfActiveCell);
// Remember old selection and select the first cell of the target
string firstCellSheet = (string)XlCall.Excel(XlCall.xlSheetNm, firstCell);
XlCall.Excel(XlCall.xlcWorkbookSelect, new object[] {firstCellSheet});
object oldSelectionOnArraySheet = XlCall.Excel(XlCall.xlfSelection);
XlCall.Excel(XlCall.xlcFormulaGoto, firstCell);
// Extend the selection to the whole array and clear
XlCall.Excel(XlCall.xlcSelectSpecial, 6);
ExcelReference oldArray = (ExcelReference)XlCall.Excel(XlCall.xlfSelection);
oldArray.SetValue(ExcelEmpty.Value);
XlCall.Excel(XlCall.xlcSelect, oldSelectionOnArraySheet);
XlCall.Excel(XlCall.xlcFormulaGoto, oldSelectionOnActiveSheet);
}
// Get the formula and convert to R1C1 mode
bool isR1C1Mode = (bool)XlCall.Excel(XlCall.xlfGetWorkspace, 4);
string formulaR1C1 = formula;
if (!isR1C1Mode)
{
// Set the formula into the whole target
formulaR1C1 = (string)XlCall.Excel(XlCall.xlfFormulaConvert, formula, true, false, ExcelMissing.Value, firstCell);
}
// Must be R1C1-style references
object ignoredResult;
XlCall.XlReturn retval = XlCall.TryExcel(XlCall.xlcFormulaArray, out ignoredResult, formulaR1C1, target);
if (retval != XlCall.XlReturn.XlReturnSuccess)
{
// TODO: Consider what to do now!?
// Might have failed due to array in the way.
firstCell.SetValue("'" + formula);
}
}
finally
{
XlCall.Excel(XlCall.xlcEcho, true);
}
}
// Most of this from the newsgroup: http://groups.google.com/group/exceldna/browse_thread/thread/a72c9b9f49523fc9/4577cd6840c7f195
private static readonly TimeSpan BackoffTime = TimeSpan.FromSeconds(1);
static void AsyncRunMacro(string macroName)
{
// Do this on a new thread....
Thread newThread = new Thread( delegate ()
{
while(true)
{
try
{
RunMacro(macroName);
break;
}
catch(COMException cex)
{
if(IsRetry(cex))
{
Thread.Sleep(BackoffTime);
continue;
}
// TODO: Handle unexpected error
return;
}
catch(Exception ex)
{
// TODO: Handle unexpected error
return;
}
}
});
newThread.Start();
}
static void RunMacro(string macroName)
{
object xlApp = null;
try
{
xlApp = ExcelDnaUtil.Application;
xlApp.GetType().InvokeMember("Run", BindingFlags.InvokeMethod, null, xlApp, new object[] {macroName});
}
catch (TargetInvocationException tie)
{
throw tie.InnerException;
}
finally
{
Marshal.ReleaseComObject(xlApp);
}
}
const uint RPC_E_SERVERCALL_RETRYLATER = 0x8001010A;
const uint VBA_E_IGNORE = 0x800AC472;
static bool IsRetry(COMException e)
{
uint errorCode = (uint)e.ErrorCode;
switch(errorCode)
{
case RPC_E_SERVERCALL_RETRYLATER:
case VBA_E_IGNORE:
return true;
default:
return false;
}
}
}
]]>
</DnaLibrary>
I think you need to implemented the request as a RTD server. Normal user defined functions will not update asynchronously.
Then you may hide the call of the RTD server via a user defined function, which can be done via Excel-DNA.
So finally you use Array formula, right? As you said, users are not familiar with array formula, they do not know ctrl+shift+enter. I think array formula is a big problem for them.
For me, I have the same issue. I am trying to build a prototype for it. see
https://github.com/kchen0723/ExcelAsync.git

Adding data to a generic string Array C#

I'm learning about generics in C#, and I'm trying to make a generic array and add some strings to it. I can easily add int values to it, but I can't figure out how to make it work with strings. When I'm trying to use strings I get NullReferenceException.
I have a class called myArray, and it looks like this:
class MyArray<T> : IComparable<T>, IEnumerable<T>, IEnumerator<T>
{
T[] data = new T[10];
int current = -1;
public T[] Data
{
get { return data; }
}
public void Add(T value)
{
for (int i = 0; i < data.Length; i++)
{
if (data[i].Equals(default(T)))
{
data[i] = value;
return;
}
}
T[] tmp = new T[data.Length + 10];
data.CopyTo(tmp, 0);
Add(value);
}
In my mainform I add the data like this:
class Program
{
static void Main(string[] args)
{
MyArray<string> StringArray = new MyArray<string>();
StringArray.Add("ONE");
StringArray.Add("TWO");
}
}
The default of string is null as it is a reference type, not a value type. You are constructing a new array of type T[], which results in an array filled with the default value of T, which in this case is null.
Following that, data[i].Equals(default(T)) throws NRE as you are trying to call null.Equals(...).
Your array is being initialized with null values, so you're getting a NRE at this line:
if (data[i].Equals(default(T)))
if (data[i].Equals(default(T))) is where the issue lies. In a new string (or any other reference type) array,
var array = new String[10];
each element in the array is null by default. So when you say
data[i].Equals(default(T)
and data[i] is null, you're calling a method on a null reference, thus causing the exception to be thrown.
This doesn't happen for value types, as the array is initialized to the default value of whatever the value type is.
This is part of the problem with generics--you can't always treat reference types and value types the same.
Try this instead, to avoid default:
private int _currentIndex = 0;
public void Add(T value)
{
data[_currentIndex] = value;
_currentIndex++;
if(_currentIndex == data.Length)
{
T[] tmp = new T[data.Length + 10];
data.CopyTo(tmp, 0);
data = tmp;
_currentIndex = 0;
}
}

Categories