Passing varying amount of arguments to VBA SubProcedures - c#

I call a few VBA methods from my C# project. This has caused me to repeat code in my project. So I created a C# method for the few that take the same amount of parameters but a few take different amounts. So I thought about a solution, all the parameters that I pass in are strings, so what if I pass in to my C# method a string array, then convert that string array in to individual strings in the VBA subprocedure call. This is what I have:
private static void RunVBAMethod(Excel.Application excelApp, string logFile, string vBAMethod, string [] args, out string errorType)
{
errorType = CSharpError;
var VbaCrashed = excelApp.Run(vBAMethod, ConvertStringArrayToString(args));
if (VbaCrashed != "False")
{
errorType = VBAError;
throw new Exception(VbaCrashed);
}
}
Here is my ConvertArrayToString method:
private static string ConvertStringArrayToString(string[] array)
{
// Concatenate all the elements into a StringBuilder.
StringBuilder builder = new StringBuilder();
foreach (string value in array)
{
builder.Append(value);
builder.Append(',');
}
return builder.ToString();
}
Then I call it like this:
RunVBAMethod(excelApp, LogFile, "CleanMRP", new string[] { CurrentWorkbook.TimePeriod, CurrentWorkbook.Version, RemoveSpace(CurrentWorkbook.ReportType), CurrentWorkbook.MainMRPFilePath + CurrentWorkbook.FileName, HeaderRow.ToString(), CurrentWorkbook.DataPath }, out ExcelCrashed);
When I run it, it crashes on the excelApp.Run line saying Parameter not optional. Is, what I am trying to do even possible? Am I looking at the wrong way to do, if it is? or am I missing something so small?

Related

Is there a way to replace the arguments in a string multiple times?

I'm declaring a string at initialisation as follows
string a = string.Format("Hello {0}", "World.");
Is there a way to subsequently replace the zeroth argument for something else?
If there's no obvious solution, does anybody know of a better way to address the issue. For context, at initialisation I create a number of strings. The text needs to be updated as the program proceeds. I could create a structure comprising an array of strings and an array of objects and then build the final string as required, but this seems ugly, particularly as each instance could have a different number of arguments.
For example,
public class TextThingy
{
List<String> strings;
List<String> arguments;
...
public string ToString()
{
return strings[0] + arguments [0] + strings [1] ...
}
I've tried this, but to no avail.
string b = string.Format(a, "Universe.");
I guess that the argument {0} once populated is then baked into the string that one time.
You could move the format string to a variable like this?
Would that work? If not, please add some more info for us.
string fmt = "Hello {0}";
string a = string.Format(fmt, "World.");
string b = string.Format(fmt, "Universe.");
try string replace, like ...
StringBuilder sb = new StringBuilder("11223344");
string myString =
sb
.Replace("1", string.Empty)
.Replace("2", string.Empty)
.Replace("3", string.Empty)
.ToString();

How to add two strings together without changing the reference

Similarly to passing a list through a function and adding elements to it, I want to pass a string and add characters to it. However, I do not want to change the reference.
Func(List myList) {
myList.Append("hello");
}
Func(List myList) {
myList = new List();
}
It's like the distinction between the two functions above. In one case you're adding an element to an existing reference to an object, in the other case you are changing the object that is referenced to.
With strings, I have noticed you always(?) change the object that is referenced to. Every solution i've found takes two or more strings, adds them together and returns a new string.
Is there a way to use the same string instance and add one or more characters to this specific instance?
With strings, I have noticed you always(?) change the object that is referenced to.
Because strings are immutable. You can't change a string in .NET. That opens the door to many optimizations (such as string interning), but also has performance issues when you want to build a long string by parts - many allocations and copyings of memory (to concatenate two strings, you have to allocate a third in the length of the two together, then copy them).
So Microsoft created System.Text.StringBuilder. The idea is to create mutable string. The basic methods are Append() (which appends some data, often primitive types) and AppendFormat() (similar to string.Format()). Then you get a normal string by calling to ToString():
void Func(StringBuilder s)
{
s.Append("Hi everyone!");
}
var s = new StringBuilder();
s.Append("a StringBuilder.");
Func(s);
s.ToString(); // "a StringBuilder.Hi everyone!"
Is there a way to use the same string instance and add one or more
characters to this specific instance?
No, but you can do it by using StringBuilder. Pass instance of StringBuilder to a function and append any string to it, it will add string but will refer to same instance of StringBuilder class
public void AppendString(StringBuilder sb) {
sb.Append("hello");
}
This is because string type is mutable, whenever you assign new value to string it creates new string object in memory, but StringBuilder is immutable, it is reference type. StringBuilder modifies without creating new object.
You can try below code,
public static void Main()
{
StringBuilder sb = new StringBuilder("Default Text");
Console.WriteLine($"Before function call: {sb.ToString()}");
AppendString(sb); //Function call
Console.WriteLine($"After function call: {sb.ToString()}");
}
public static void AppendString(StringBuilder sb)
{
sb.Append(" Hello world");
Console.WriteLine($"Inside function: {sb.ToString()}");
}
Output:
Before function call: Default Text
Inside function: Default Text Hello world
After function call: Default Text Hello world
.Net fiddle
I would suggest you to read string vs StringBuilder
You can achieve this by passing string parameter as reference. Please refer to the following code snippet.
static void Main(string[] args)
{
string input = "input";
AddString(ref input);
System.Console.WriteLine(input);
}
private static void AddString(ref string input)
{
input += "_edited";
}
You will need to use ref keyword in both the cases while defining and passing method parameter. Hope it helps.

Ranorex Passing Array as Parameter

I have some calculations to complete. I want to send my array to a method that does some calculations. How do I format the sending method here? How do I format the receiving signature line of the receipient method? How do I split the array to use each element?
public void PercentCompare()
{
string[] cells = {
"Repo.Path.ToString()",
"Repo.Path.ToString()",
"Repo.Path.ToString()"
};
Path.CountDoublePercent(cells[]);
}
public static void CountD(string[] cells)
{
//trims
string trimRow1 = string.Empty;
string trimRow2 = string.Empty;
string trimTotal = string.Empty;
}
http://www.ranorex.com/forum/passing-array-as-usercode-argument-t8098.html
this site says: it’s not possible to use an array as an argument for UserCode methods
Can you really do it with no errors?

Pass an array to a function (And use the function to split the array)

I want to pass a string array (separated by commas), then use a function to split the passed array by a comma, and add in a delimiter in place of the comma.
I will show you what I mean in further detail with some broken code:
String FirstData = "1";
String SecondData = "2" ;
String ThirdData = "3" ;
String FourthData = null;
FourthData = AddDelimiter(FirstData,SecondData,ThirdData);
public String AddDelimiter(String[] sData)
{
// foreach ","
String OriginalData = null;
// So, here ... I want to somehow split 'sData' by a ",".
// I know I can use the split function - which I'm having
// some trouble with - but I also believe there is some way
// to use the 'foreach' function? I wish i could put together
// some more code here but I'm a VB6 guy, and the syntax here
// is killing me. Errors everywhere.
return OriginalData;
}
Syntax doesn't matter much here, you need to get to know the Base Class Library. Also, you want to join strings apparently, not split it:
var s = string.Join(",", arrayOFStrings);
Also, if you want to pass n string to a method like that, you need the params keyword:
public string Join( params string[] data) {
return string.Join(",", data);
}
To split:
string[] splitString = sData.Split(new char[] {','});
To join in new delimiter, pass in the array of strings to String.Join:
string colonString = String.Join(":", splitString);
I think you are better off using Replace, since all you want to do is replace one delimiter with another:
string differentDelimiter = sData.Replace(",", ":");
If you have several objects and you want to put them in an array, you can write:
string[] allData = new string[] { FirstData, SecondData, ThirdData };
you can then simply give that to the function:
FourthData = AddDelimiter(allData);
C# has a nice trick, if you add a params keyword to the function definition, you can treat it as if it's a function with any number of parameters:
public String AddDelimiter(params String[] sData) { … }
…
FourthData = AddDelimiter(FirstData, SecondData, ThirdData);
As for the actual implementation, the easiest way is to use string.Join():
public String AddDelimiter(String[] sData)
{
// you can use any other string instead of ":"
return string.Join(":", sData);
}
But if you wanted to build the result yourself (for example if you wanted to learn how to do it), you could do it using string concatenation (oneString + anotherString), or even better, using StringBuilder:
public String AddDelimiter(String[] sData)
{
StringBuilder result = new StringBuilder();
bool first = true;
foreach (string s in sData)
{
if (!first)
result.Append(':');
result.Append(s);
first = false;
}
return result.ToString();
}
One version of the Split function takes an array of characters. Here is an example:
string splitstuff = string.Split(sData[0],new char [] {','});
If you don't need to perform any processing on the parts in between and just need to replace the delimiter, you could easily do so with the Replace method on the String class:
string newlyDelimited = oldString.Replace(',', ':');
For large strings, this will give you better performance, as you won't have to do a full pass through the string to break it apart and then do a pass through the parts to join them back together.
However, if you need to work with the individual parts (to recompose them into another form that does not resemble a simple replacement of the delimiter), then you would use the Split method on the String class to get an array of the delimited items and then plug those into the format you wish.
Of course, this means you have to have some sort of explicit knowledge about what each part of the delimited string means.

Parsing formatted string

I am trying to create a generic formatter/parser combination.
Example scenario:
I have a string for string.Format(), e.g. var format = "{0}-{1}"
I have an array of object (string) for the input, e.g. var arr = new[] { "asdf", "qwer" }
I am formatting the array using the format string, e.g. var res = string.Format(format, arr)
What I am trying to do is to revert back the formatted string back into the array of object (string). Something like (pseudo code):
var arr2 = string.Unformat(format, res)
// when: res = "asdf-qwer"
// arr2 should be equal to arr
Anyone have experience doing something like this? I'm thinking about using regular expressions (modify the original format string, and then pass it to Regex.Matches to get the array) and run it for each placeholder in the format string. Is this feasible or is there any other more efficient solution?
While the comments about lost information are valid, sometimes you just want to get the string values of of a string with known formatting.
One method is this blog post written by a friend of mine. He implemented an extension method called string[] ParseExact(), akin to DateTime.ParseExact(). Data is returned as an array of strings, but if you can live with that, it is terribly handy.
public static class StringExtensions
{
public static string[] ParseExact(
this string data,
string format)
{
return ParseExact(data, format, false);
}
public static string[] ParseExact(
this string data,
string format,
bool ignoreCase)
{
string[] values;
if (TryParseExact(data, format, out values, ignoreCase))
return values;
else
throw new ArgumentException("Format not compatible with value.");
}
public static bool TryExtract(
this string data,
string format,
out string[] values)
{
return TryParseExact(data, format, out values, false);
}
public static bool TryParseExact(
this string data,
string format,
out string[] values,
bool ignoreCase)
{
int tokenCount = 0;
format = Regex.Escape(format).Replace("\\{", "{");
for (tokenCount = 0; ; tokenCount++)
{
string token = string.Format("{{{0}}}", tokenCount);
if (!format.Contains(token)) break;
format = format.Replace(token,
string.Format("(?'group{0}'.*)", tokenCount));
}
RegexOptions options =
ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None;
Match match = new Regex(format, options).Match(data);
if (tokenCount != (match.Groups.Count - 1))
{
values = new string[] { };
return false;
}
else
{
values = new string[tokenCount];
for (int index = 0; index < tokenCount; index++)
values[index] =
match.Groups[string.Format("group{0}", index)].Value;
return true;
}
}
}
You can't unformat because information is lost. String.Format is a "destructive" algorithm, which means you can't (always) go back.
Create a new class inheriting from string, where you add a member that keeps track of the "{0}-{1}" and the { "asdf", "qwer" }, override ToString(), and modify a little your code.
If it becomes too tricky, just create the same class, but not inheriting from string and modify a little more your code.
IMO, that's the best way to do this.
It's simply not possible in the generic case. Some information will be "lost" (string boundaries) in the Format method. Assume:
String.Format("{0}-{1}", "hello-world", "stack-overflow");
How would you "Unformat" it?
Assuming "-" is not in the original strings, can you not just use Split?
var arr2 = formattedString.Split('-');
Note that this only applies to the presented example with an assumption. Any reverse algorithm is dependent on the kind of formatting employed; an inverse operation may not even be possible, as noted by the other answers.
A simple solution might be to
replace all format tokens with (.*)
escape all other special charaters in format
make the regex match non-greedy
This would resolve the ambiguities to the shortest possible match.
(I'm not good at RegEx, so please correct me, folks :))
After formatting, you can put the resulting string and the array of objects into a dictionary with the string as key:
Dictionary<string,string []> unFormatLookup = new Dictionary<string,string []>
...
var arr = new string [] {"asdf", "qwer" };
var res = string.Format(format, arr);
unFormatLookup.Add(res,arr);
and in Unformat method, you can simply pass a string and look up that string and return the array used:
string [] Unformat(string res)
{
string [] arr;
unFormatLoopup.TryGetValue(res,out arr); //you can also check the return value of TryGetValue and throw an exception if the input string is not in.
return arr;
}

Categories