string.Format fails at runtime with array of integers - c#

Consider string.Format() whose parameters are a string and, among others in the overload list, an object[] or many objects.
This statement succeeds:
string foo = string.Format("{0} {1}", 5, 6);
as does this:
object[] myObjs = new object[] {8,9};
string baz = string.Format("{0} and {1}", myObjs;
as does an array of strings:
string[] myStrings = new string[] {"abc", "xyz"};
string baz = string.Format("{0} {1}", myStrings);
It seems that the integers, when specified individually, can be boxed or coerced to type object, which in turn is coerced to a string.
This statement fails at runtime.
int[] myInts = new int[] {8,9};
string bar = string.Format("{0} and {1}", myInts);
Index (zero based) must be greater than or equal to zero and less than the size of the argument list.
Why doesn't or can't the int array be coerced or boxed to an object[] or string[]?
Out of a small bit of curiosity, why doesn't the compiler catch this?

The call fails with the same reason the following will also fail:
string foo = string.Format("{0} {1}", 5);
You are specifying two arguments in the format but only specifying one object.
The compiler does not catch it because int[] is passed as an object which is a perfectly valid argument for the function.
Also note that array covariance does not work with value types so you cannot do:
object[] myInts = new int[] {8,9};
However you can get away with:
object[] myInts = new string[] { "8", "9" };
string bar = string.Format("{0} {1}", myInts);
which would work because you would be using the String.Format overload that accepts an object[].

Your call gets translated into this:
string foo = string.Format("{0} {1}", myInts.ToString());
which results in this string:
string foo = "System.Int32[] {1}";
So as the {1} doesn't have a parameter, it throws an exception

I think the concept you are having an issue with is why int[] isn't cast to object[]. Here's an example that shows why that would be bad
int[] myInts = new int[]{8,9};
object[] myObjs = (object[])myInts;
myObjs[0] = new object();
The problem is that we just added an object into a int array.
So what happens in your code is that myInts is cast to object and you don't have a second argument to fill in the {1}

Short way to make it work (not the most optimal though):
int[] myInts = new int[] { 8, 9 };
string[] myStrings = Array.ConvertAll(myInts, x => x.ToString());
// or using LINQ
// string[] myStrings = myInts.Select(x => x.ToString()).ToArray();
bar = string.Format("{0} and {1}", myStrings);

This is quite an old question, but I recently got the same issue. And I haven't seen an answer that works for me, so I'll share the solution I found.
Why doesn't or can't the int array be coerced or boxed to an object[] or string[]? Why it isn't boxed, I don't know. But it can be boxed explicitly, see solution below.
Why doesn't the compiler catch this? Because the compiler misinterprets the situation: The type isn't exactly an object array, so it doesn't know what to do with it and decides to perform a .ToString() on the int array, which returns one single parameter containing the type name rather than the parameter list itself. It doesn't do that with a string array, because the target type is already a string - but with any other kind of array the same issue happens (for example bool[]). Consider var arr1 = new int[]{1,2}; with string.Format("{0}", arr1): As long as you have only {0} in the format string, you get only the type name "System.Int32[]" back (and no exception occurs). If you have more placeholders, e.g. string.Format("{0}{1}", arr1), then the exception occurs - because arr1 is misinterpreted as one parameter - and for the compiler, a 2nd one is missing. But what I think is a conceptional bug is that you can't convert arr1, i.e. if you try to do (object[])arr1- you're getting:
CS0030 Cannot convert type 'int[]' to 'object[]'
Solution:
Filling in each element of the int array is not a solution that works for me, because in my project I am creating a format template string dynamically during runtime containing the {0}...{n} - hence I need to pass an array to String.Format.
So I found the following workaround. I created a generic helper function (which of course could be an extension method too if you prefer):
// converts any array to object[] and avoids FormatException
object[] Convert<T>(T[] arr)
{
var obj = new List<object>();
foreach (var item in arr)
{
obj.Add((object)item);
}
return obj.ToArray();
}
Now if you try that in the example below which is showing up the FormatException:
// FormatException: Index (zero based) must be greater than or equal to zero
// and less than the size of the argument list
var arr1 = (new int[] { 1, 2 });
string.Format("{0}{1}{0}{1}", arr1).Dump();
Fix: Use Convert(arr1) as 2nd parameter for string.Format(...) as shown below:
// Workaround: This shows 1212, as expected
var arr1 = (new int[] { 1, 2 });
string.Format("{0}{1}{0}{1}", Convert(arr1)).Dump();
Try example as DotNetFiddle
Conclusion:
As it seems, the .NET runtime really misinterprets the parameter by applying a .ToString() to it, if it is not already of type object[]. The Convert method gives the runtime no other choice than to do it the right way, because it returns the expected type. I found that an explicit type conversion did not work, hence the helper function was needed.
Note: If you invoke the method many times in a loop and you're concerned about speed, you could also convert everything to a string array which is probably most efficient:
// converts any array to string[] and avoids FormatException
string[] ConvertStr<T>(T[] arr)
{
var strArr = new string[arr.Length];
for (int i = 0; i < arr.Length; i++)
{
strArr[i]=arr[i].ToString();
}
return strArr;
}
This is working as well. To convert from a different datatype, such as a dictionary, you can simply use
string[] Convert<K,V>(Dictionary<K,V> coll)
{
return ConvertStr<V>(coll.Values.ToArray());
}
Update: With string interpolation, another short way to solve it is:
var baz = string.Format("{0} and {1}", myInts.Select(s => $"{s}").ToArray());

Your string.Format is expecting 2 arguments ({0} and {1}). You are only supplying 1 argument (the int[]). You need something more like this:
string bar = string.Format("{0} and {1}", myInts[0], myInts[1]);
The compiler does not notice the problem because the format string is evaluated at runtime. IE The compiler doesn't know that {0} and {1} mean there should be 2 arguments.

This works:
string bar = string.Format("{0} and {1}", myInts[0], myInts[1]);
The compiler doesn't catch it because it doesn't evaluate your format string.
The example you gave up top doesn't match what you're trying to do down below... you provided two {} and two arguments, but in the bottom one you only provided one argument.

Related

Index of the string can be long?

I want to know whether we can give the index of the string as Long data type.
var i=long.Parse(Console.ReadLine());
var result = testString[i-1];
the second line giving me the error by saying that "The best overloaded method match for 'string.this[int]' has some invalid arguments."
No you can't use long for most collection types (you haven't specified what testString is).
One way to get around this would be to segregate the string into a multi-part / multi-dimension array then use a multiplier to get which part of the array to check.
For example:
Your index is 100,000 and you have an array of shorts (32,767 length)...
string[,] testString = new string[100, 32766]; //Replace this with your Initialisation / existing string
var arrayRank = (int)Math.Round((double) 100000 / 32767, 0);
var arrayIndex = (int)Math.Round((double)100000 % 32767, 0);
//Test this works.
//testString[arrayRank, arrayIndex] = "test"; - Test to see that the array range is assignable.
var result = testString[arrayRank, arrayIndex]; //Test value is what we expect
This may not be the most efficient way to go about things, but it is a workaround.
No, it cannot accept a long. The only overload accepts an int indexer. You would need to change your code to int.Parse() instead of long.Parse()
There is no way to pass long as an index of array, compiler doesn't allow that.
Workaround can be converting the long to int, this is called narrow conversion.
var result= testString[(int)i)];

c# place string into an array

This could be very easy, but how can I place or convert a string into an array?
The code that I have, is the following:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string one;
string[] two;
one = "Juan";
two = {one}; // here is the error
HttpContext.Current.Response.Write(two);
}
}
And the error is the following:
Compiler Error Message: CS0029: Cannot implicitly convert type 'string' to 'string[]'
Thanks for you help!
Replace this:
two = {one}; // here is the error
With
two = new[] { one };
OR
two = new string[] { one };
The reason you are getting the error is clear from the error message.
See: Object and Collection Initializers (C# Programming Guide)
Later when you are doing Response.Write, you will get System.String[] as output, since two is an array. I guess you need all array elements separated by some delimiter. You can try:
HttpContext.Current.Response.Write(string.Join(",", two));
Which will produce all the elements in the array separated by comma
It looks like you're trying to use initialization syntax for an assignment. This should work:
two = new string[] {one};
or just
two = new [] {one};
since the compiler will infer that you want a string[]
I think you'll also be surprised what Response.Write(two); produces...
You're using the static initializer syntax to try and add an item to your array. That doesn't work. You can use similar syntax to allocate a new array with the value one - two = new string[] { one }; - or you can allocate the array then add elements through assignment like;
string[] two = new string[10];
two[0] = one; // assign value one to index 0
If you do it like this you have to do some bounds checking for example the following will throw an IndexOutOfRangeException at runtime;
string[] two = new string[10];
int x = 12;
two[x] = one; // index out of range, must ensure x < two.Length before trying to assign to two[x]
That syntax ({one}) is only valid if you declare the array variable in the same line. So, this works:
string one;
one = "Juan";
string[] two = {one};
A more common way to initialize an array, which works in more places, is to use the new keyword, and optionally have the type be inferred, e.g.
string one;
string[] two;
one = "Juan";
// type is inferrable, since the compiler knows one is a string
two = new[] {one};
// or, explicitly specify the type
two = new string[] {one};
I usually declare and initialize on the same line, and use var to infer the type, so I'd probably write:
var one = "Juan";
var two = new[] { one };

Why Does String.Format Discriminate? [duplicate]

This question already has answers here:
string.Format fails at runtime with array of integers
(7 answers)
Closed 9 years ago.
String.Format will happily work correctly with an array of strings, but fails when dealing with an array of ints with exception :
Index (zero based) must be greater than or equal to zero and less than
the size of the argument list.
string result = null;
var words = new string[] { "1", "2", "3" };
result = String.Format("Count {0}{1}{2}", words); //This works.
var nums = new int[] { 1, 2, 3 };
result = String.Format("Count {0}{1}{2}", nums); //This throws an exception.
Why is this so?
This happens because the string.Format overload you are using wants object[]. A string is a reference type, so string[] can be implicitly cast to object[], but int is a value type, and would have to be boxed before being put in an array of objects. So when you're using int it selects another overload that just takes one parameter, and then passes the entire int[] as a single object instead of passing each int by itself.
Because ToString() method is called for Array of ints. And it's becomes 1 object.
This code:
var nums = new int[] { 1, 2, 3 };
result = String.Format("Count {0}", nums);
Will result:
Count System.Int32[]

How to convert object to object[]

I have an object whose value may be one of several array types like int[] or string[], and I want to convert it to a string[]. My first attempt failed:
void Do(object value)
{
if (value.GetType().IsArray)
{
object[] array = (object[])value;
string[] strings = Array.ConvertAll(array, item => item.ToString());
// ...
}
}
with the runtime error Unable to cast object of type 'System.Int32[]' to type 'System.Object[]', which makes sense in retrospect since my int[] doesn't contain boxed integers.
After poking around I arrived at this working version:
void Do(object value)
{
if (value.GetType().IsArray)
{
object[] array = ((Array)value).Cast<object>().ToArray();
string[] strings = Array.ConvertAll(array, item => item.ToString());
// ...
}
}
I guess this is OK, but it seems pretty convoluted. Anyone have a simpler way?
You don't need to convert it to an array and then use LINQ. You can do it in a more streaming fashion, only converting to an array at the end:
var strings = ((IEnumerable) value).Cast<object>()
.Select(x => x == null ? x : x.ToString())
.ToArray();
(Note that this will preserve nulls, rather than throwing an exception. It's also fine for any IEnumerable, not just arrays.)
.ToArray makes multiple memory allocations in most cases, but there are few ways around it:
object value = new[] { 1, 2.3 };
IList list = value as IList;
string[] strings = new string[list.Count];
for (int i = 0; i < strings.Length; i++)
strings[i] = Convert.ToString(list[i]);
In most cases that might be a bit overkill and waste of vertical space, so I would use something like the accepted answer with an optional null-conditional operator ? to check if the source is array:
string[] strings = (value as Array)?.Cast<object>().Select(Convert.ToString).ToArray();
void Do(object value)
{
if (value.GetType().IsArray)
{
string[] strings = ((object[]) value).Select(obj => Convert.ToString(obj)).ToArray();
}
}

Equivalent of func_get_arg in C#?

Is there an equivalent to func_get_arg (php) in C#?
func_get_arg ( int $arg_num ):
Gets the specified argument from a user-defined function's argument list.
This function may be used in conjunction with func_get_args() and func_num_args() to allow user-defined functions to accept variable-length argument lists.
It basically means the index can be used to get the argument value...
Thanks
C# is statically typed, so function signatures matter. You can't just call a method with any number of arguments, which really means there is no need for func_get_arg.
That said, you can get pretty close if you have a method such as this one:
void MyMethod(params object[] args)
{
var indexOfArgument = 42; // or whatever
var valueOfArgument = args[indexOfArgument]; // should also check array bounds
}
Of course if all your arguments are typed as System.Object there's not much you can do with them, but from a syntactic viewpoint it's close (plus, you could also have a method that accepts params T[] args for any type T).
in C# there's a method "__arglist()"
or you can easily make a function accept variable number of parameters like this
int Average(params int[] ints)
{
int total = 0;
foreach(int i in ints) // loop to the number of parameters
total += i;
return (ints.Length > 0 ? total / ints.Length : 0);
}
As I understand it, the purpose of func_get_arg in PHP is to have a variable number of arguments to a function. Here's the equivilent to that in C#:
public void Foo(string realArg, int anotherRealArg, params int[] variableArgs)
{
Console.WriteLine("My real string argument was " + realArg);
Console.WriteLine("My real integer argument was " + anotherRealArg);
Console.WriteLine("And I was given " + variableArgs.Length + " extra arguments");
}
// Usage
Foo("Bar", 1, 2, 3, 4, 5);
Within the method, variableArgs is a regular array. Before accessing it, you'll want to check its Length to be sure you don't get an IndexOutOfRangeException.

Categories