Trouble using ExcelRange.ToArray<>() in EPPlus - c#

I am using EPPlus and don't seem to be able to get the ToArray<> method to work. I am trying to pull out an array of string variables of column headers in a worksheet.
My code...
public static string[] GetWshHeaders(string WbkNm)
{
using (ExcelPackage package = new ExcelPackage(new FileInfo(WbkNm)))
{
ExcelWorksheet wsData = package.Workbook.Worksheets.First();
int noHdrs = wsData.Dimension.Columns;
ExcelRange hdrs = wsData.Cells[1, 1, 1, noHdrs];
string[] wsHdrs = hdrs.ToArray<string>();
return wsHdrs;
}
}
Intellisense flags the hdrs variable in the line string[] wsHdrs = hdrs.ToArray();. The message is: 'ExcelRange' does not contain a definition for 'ToArray' and the best extension method overload 'Enumerable.ToArray(IEnumerable)' requires a receiver of type 'IEnumerable'.
I have played around with any number of variations of the above code but, well, I wouldn't be posting this question if I had hit upon the correct syntax.
Any help would be appreciated!

ExcelRange has ExcelRangeBase as a base class which is declared list this:
public class ExcelRangeBase : ExcelAddress, IExcelCell, IDisposable, IEnumerable<ExcelRangeBase>, IEnumerator<ExcelRangeBase>
{
....
}
So you are trying to use Linq to do an implicit cast from IEnumerable<ExcelRangeBase> to String[] which will not work. You need to use a Select to get the Value properties of each range object. And since each Value property is of type Object you will need to call its ToString() method:
string[] wsHdrs = hdrs
.Select(erb => erb.Value.ToString())
.ToArray();
The above will get you the array of strings you are looking for but keep in mind that you are loosing information since Value can be a mix of numeric types and string. Not a problem if you are only interested in, say, simply printing their content. If you plan to write them back in some way to excel you will have everything as string.

Related

Excel.Workbook as a Global variable C#

I am trying to read and write to several excel sheets and I have split the functions into different methods.
The problem I am encountering is I cannot carry over the Workbook/Worksheet names over into the second method.
First method: Open all the relevant excel documents i.e Parts list, Export list etc.
Second method: Copy data from Parts List to first sheet in Export list.
For example in the first method I may have
//Ws = Worksheet
//Wb = Workbook
//Workbooks and applications already defined
var PartsExportWs = PartsExportWb.Sheets[1].Name;
In the Second method I have:
public static void Parts
{
int PartsCounterX;
int TypicalCounterY = 4;
int NullCounter = 0;
var ConCatPartsCellValue = new System.Text.StringBuilder();
for (PartsCounterX = 1; NullCounter <= 3; ++PartsCounterX)
{
var PartsCellValue = PartsExportWs.Cells[TypicalCounterY, PartsCounterX].Value;
// etc ...
However, it errors out at PartsExportWs with the Description of "The name PartsExportWs does not exist in the current context"
I may be wrong but I am assuming that it is due to the fact it is not classed as a Global variable.
(If anyone has any suggestions it would be more than helpful. Even if it is on how to ask the question better!)
You cannot access a local variable from one method in the code from another method.
You either need to pass the variable as a parameter, or store it in a field.
(There is no such thing as a "global variable" in C#.)

c#, class with properties of type string array, set not working

I have a class that has several properties, a couple which are string arrays. When the following statement:
MyObj.Str1[1] = "AAA";
MyObj.Str1[1] does not contain "AAA". Using debug breakpoints, I see the set routine doesn't execute (the get routine does execute).
The property looks like:
public string[] Str1
{
get { return bdr.GetArrVal(1, 25, 7); }
set { bdr.SetArrVal(1, 25, 7, value); }
}
GetArrVal() builds and returns a string array from class internal data. SetArrVal() sets class internal data from the incoming array.
I tried using indexers but had too many problems passing class internal data into the class describing Str1.
Please note that the statement
MyObj.Str1 = arr1;
works, where arr1 is a string array. The program breaks at the set routine.
All of this makes me think I cant do what I want. Can you assign a single element of a string-array property of an object?
MyObj.Str1 is a string (which is a group of characters) and MyObj.Str1[1] is the char on the second index of that string. You are assigning "AAA" three elements to a single character. This does not make any sense if you are trying to "assign a single element of a string-array property of an object?"
Str1 is the property name as posted by you public string[] Str1 { which returns a string[]. So when you say MyObj.Str1[1] = "AAA"; you are actually trying to set a array element returned by Str1 property and not the property itself and thus the statement MyObj.Str1 = arr1; works fine.

Read CSV File and Store Column in list double

carry the code below where I embarked.
class readFile{
List<double> out1 = new List<double>();
List<double> out2 = new List<double>();
List<double> out3 = new List<double>();
public readFile()
{
}
public void aproCSV()
{
var reader = new StreamReader(File.OpenRead(#"C:\altraprova.csv"));
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(';');
out1.Add(values[0]);
out2.Add(values[1]);
out3.Add(values[2]);
}
}
}
Allow me to explain my intent ....
I open my csv file
the goal is to read the second column and insert it into a list of type double and then get the value MAX.
If you use lists of type string not get erors .... but since the values contain only numbers you are signed semicolon, then I need to use the type double.. using the double type get the following error:
error 5 The best match of the overloaded method for
'System.Collections.Generic.List <double> .Add (double)' has some
invalid arguments
Split() returns a string[] and your lists are of type List<double> which means you need to parse the strings into doubles like the following:
double value1 = double.Parse(values[0]); etc
then add them to your list: out1.Add(value1);
Do be aware that you have no error handling so if the value is not a double, the code will still throw an exception.
CSV are surprisingly not so straightforward to parse, there are a lot of special cases which you have to take into consideration. For example, if your data would contain the "separation character" you'd need to put the data between quotes ("). If he contains quotes, then you have to escape them with a backslash (\) (or doubling them, I'm not sure).
So, except if you know the data you're going to be importing and are sure that those case won't happen, a simple "split" won't be sufficient.
I really recommend using an existing parser to help you on this task. I've used CsvHelper with success. It's a nice library and quite easy to use.
You need to do some parsing, because a generic list only accepts elements of the given type (double in your case):
var line = reader.ReadLine();
var values = line.Split(';');
out1.Add(double.Parse(values[0]));
out2.Add(double.Parse(values[1]));
out3.Add(double.Parse(values[2]));
This is the quick and dirty trick, you should use double.TryParse
for safety because maybe not everything you get is a double.
From you code I suppose the you need to get the max from list out2 (your second column). For this, use List.Max Method

Can I GET and SET an array in C#?

HOMEWORK QUESTION:
I need to create a simple trivia game that reads from a CSV file. My data for a particular question is structured as follows: "Question;AnswerA;AnswerB;AnswerC;AnswerD;CorrectAnswerLetter".
We're using a series of getters and setters to hold all the relevant data for a single question object, and I'm running into a problem with the array I've created to hold the four answers.
In my constructor, I'm using this code--which I believe instantiates the Answer array in question:
class TriviaQuestionUnit
{
...
const int NUM_ANSWERS = 4;
string[] m_Answers = new String[NUM_ANSWERS];
public string[] Answer
{
get { return m_Answers[]; }
set { m_Answers = value[];
}
...
// Answer array
public string[] GETAnswer(int index)
{
return m_Questions[index].Answer;
}
...
}
I'm accessing the getter and setter from my TriviaQuestionBank method, which includes this code:
...
const int NUM_QUESTIONS = 15;
TriviaQuestionUnit[] m_Questions = new TriviaQuestionUnit[NUM_QUESTIONS];
...
// Answer array
public string[] GETAnswer(int index)
{
return m_Questions[index].Answer;
}
...
I'm using using StreamReader to read a line of input from my file
...
char delim = ';';
String[] inputValues = inputText.Split(delim);
...
parses the input in an array from which I create the question data. For my four answers, index 1 through 4 in the inputValues array, I populate this question's array with four answers.
...
for (int i = 0; i < NUM_ANSWERS; i++)
{
m_Questions[questionCounter].Answer[i] = inputValues[i + 1];
}
...
I'm getting errors of Syntax code, value expected on the getters/setters in my constructor, and if I change the variable to m_Answers[NUM_QUESTIONS] I get an error that I can't implicitly convert string to String[].
Hopefully I've posted enough code for someone to help point me in the right direction. I feel like I'm missing something obvious, but I just cannot make this work.
Your code has some errors that will cause compilation errors, so my first lesson for you is going to be: listen to the compiler. Some of the errors might seem a bit hard to understand sometimes, but I can ensure you that a lot of other people have had the same problems before; googling a compiler error often gives you examples from other people that are similar to your issue.
You say "In my constructor", but the problem is that your code does not have a constructor. You do however initialize fields and properties on your class and surely enough, the compiler will create a default constructor for you, but you have not defined one yourself. I am not saying that your code does not work because you do not have a constructor, but you might be using the wrong terms.
The first problem is in your first code snippet inside TriviaQuestionUnit. Your first two lines are working correctly, you are creating a constant integer with the value 4 that you use to determine how large your array is going to be and then you initialize the array with that given number.
When you do new string[NUM_ANSWERS] this will create an array, with default (empty) values.
The first problem that arises in your code is the getters and setters. The property expects you to return an array of strings which the method signature in fact is telling us:
public string[] Answer
However, looking at the getter and setter, what is it that you return?
m_Answers is a "reference" to your array, hence that whenever you write m_Answers you are referring to that array. So what happens when we add the square brackets?
Adding [] after the variable name of an array indicates that we want to retrieve a value from within the array. This is called the indexer, we supply it with an index of where we want to retrieve the value from within the array (first value starts at index 0). However, you don't supply it with a value? So what is returned?
Listen to the compiler!
Indexer has 1 parameter(s) but is invoked with (0) argument(s)
What does this tell you? It tells you that it doesn't expect the empty [] but it would expect you to supply the indexer with a number, for instance 0 like this: [0]. The problem with doing that here though, is that this would be a miss-match to the method signature.
So what is it that we want?
We simply want to return the array that we created, so just remove [] and return m_Answers directly like this:
public string[] Answer
{
get { return m_Answers; }
set { m_Answers = value; }
}
Note that you were also missing a curly bracket at the end if the set.
When fixing this, there might be more issues in your code, but trust the compiler and try to listen to it!

C#: Dynamically Constructing Variables

I get from an input a group of double variables named: weight0, weight1...weight49.
I want to dynamically insert them into a double Array for easier manipulation.
But instead of calling each one like: Weights[0] = weight0...Weights[49] = weight49 I want to do it with a single loop.
Is there a way to do it?
No, basically - unless you mean at the same time that you create the array:
var weights = new[] {weight0, weight1, weight2, ... , weight48, weight49};
Personally, I'd be tempted to get rid of the 50 variables, and use the array from the outset, but that may not be possible in all cases.
you could use reflection to determine the index of the array from the variable names but this is far from efficient. See this post for details.
I would try to do it with a KeyValuePair- Listobject
// sample data
var weight = 1.00;
// create a list
var tmp = new List<KeyValuePair<string,object>>();
// Here you can add your variables
tmp.Add(new KeyValuePair<string,object>("weights" + tmp.Count.ToString()
, weight));
// If needed convert to array
var weights = tmp.ToArray();
// get the information out of the array
var weightValue = weights[0].Value;
var weightKey = weights[0].Key;
I think this will give you all the options, you might need for the array. Give it a try.
I'm putting this up because you can do it - so long as these variables are actually fields/properties. Whether you should is another matter - this solution, while reusable, is slow (needs delegate caching) and I have to say I agree with Marc Gravell - consider using an array throughout if you can.
If the variables are properties then it needs changing. Also if you need to write the array back to the variables in one shot (because this solution generates an array with copies of all the doubles, I wouldn't consider creating an object array with boxed doubles), that requires another method...
So here goes. First a holy wall of code/extension method:
//paste this as a direct child of a namespace (not a nested class)
public static class SO8877853Extensions
{
public static TArray[] FieldsToArray<TObj, TArray>(this TObj o,string fieldPrefix)
{
if(string.IsNullOrWhiteSpace(fieldPrefix))
throw new ArgumentException("fieldPrefix must not null/empty/whitespace",
"fieldPrefix");
//I've done this slightly more expanded than it really needs to be...
var fields = typeof(TObj).GetFields(System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.NonPublic)
.Where(f =>f.Name.StartsWith(fieldPrefix) && f.FieldType.Equals(typeof(TArray)))
.Select(f =>new{ Field = f, OrdinalStr = f.Name.Substring(fieldPrefix.Length)})
.Where(f => { int unused; return int.TryParse(f.OrdinalStr, out unused);})
.Select(f => new { Field = f.Field, Ordinal = int.Parse(f.OrdinalStr) })
.OrderBy(f => f.Ordinal).ToArray();
//doesn't handle ordinal gaps e.g. 0,1,2,7
if(fields.Length == 0)
throw new ArgumentException(
string.Format("No fields found with the prefix {0}",
fieldPrefix),
"fieldPrefix");
//could instead bake the 'o' reference as a constant - but if
//you are caching the delegate, it makes it non-reusable.
ParameterExpression pThis = Expression.Parameter(o.GetType());
//generates a dynamic new double[] { var0, var1 ... } expression
var lambda = Expression.Lambda<Func<TObj, TArray[]>>(
Expression.NewArrayInit(typeof(TArray),
fields.Select(f => Expression.Field(pThis, f.Field))), pThis);
//you could cache this delegate here by typeof(TObj),
//fieldPrefix and typeof(TArray) in a Dictionary/ConcurrentDictionary
return lambda.Compile()(o);
}
}
The extension method above will work on any type. It's generic over both the instance type and desired array type to simplify the creation of the lambda in code - it doesn't have to be generic though.
You pass in the name prefix for a group of fields - in your case "weight" - it then searches all the public and private instance fields for those with that prefix that also have a suffix which can be parsed into an integer. It then orders those fields based on that ordinal. It does not check for gaps in the ordinal list - so a type with weight0 and weight2 would work, but would only create a two-element array.
Then it bakes a dynamic piece of code using Expression trees, compiles it (at this point, as mentioned in the code, it would be good to cache the delegate against TObj and TArray for future use) and then executes it, returning the result.
Now add this to a test class in a standard unit test project:
private class SO8877853
{
private double field0 = 1.0;
private double field1 = -5.0;
private double field2 = 10.0;
public double[] AsArray()
{
//it would be nice not to have to pass both type names here - that
//can be achieved by making the extension method pass out the array
//via an 'out TArray[]' instead.
return this.FieldsToArray<SO8877853, double>("field");
}
}
[TestMethod]
public void TestThatItWorks()
{
var asArray = new SO8877853().AsArray();
Assert.IsTrue(new[] { 1.0, -5.0, 10.0 }.SequenceEqual(asArray));
}
Like I say - I'm not condoning doing this, nor am I expecting any +1s for it - but I'm a sucker for a challenge :)

Categories

Resources