I read values from an local Access mdb-file. One value is stored as string in the db and I have it in a table. When using the GetType() method it return "System.String" and I can print it on the console without a problem but when I want to use it as an attribute for another method (requires a string) I get an error ("Cannot convert from 'object' to 'string'" and the same for 'int'). The same problems occur with some int values.
Am I doing something wrong or what is the problem in that case?
Console.WriteLine(dt.Rows[0][ProjName]); //prints project_name
Console.WriteLine(dt.Rows[0][ProjName].GetType()); //print "System.String"
Project = new Project(dt.Rows[0][ProjName], dt.Rows[0][MinDay], dt.Rows[0][MinWeek], dt.Rows[0][DayWeek]); //Error
Project = new Project(Convert.ToString(dt.Rows[0][ProjName]), Convert.ToInt32(dt.Rows[0][MinDay]), Convert.ToInt32(dt.Rows[0][MinWeek]), Convert.ToInt32(dt.Rows[0][DayWeek])); //Works Fine
Constructor for the Project Class:
public Project(string projectName, int hoursPerDay, int hoursPerWeek, int daysPerWeek)
You have stated in your answer is works when converting, and it is necessary as they are not strings and integers. They are objects. You can create a methid to handle it if you want.
public Project CreateProject(object projectName, object hoursPerDay, object hoursPerWeek, object daysPerWeek)
{
return new Project(projectName.ToString(), Convert.ToInt32(hoursPerDay), Convert.ToInt32(hoursPerWeek), Convert.ToInt32(daysPerWeek);
}
You have to explicitly cast the objects:
To cast to string use:
Object.ToString();
To cast to integers use:
Int32.TryParse(String, out int);
Your constuctor becomes
Project = new Project(dt.Rows[0][ProjName].ToString(), Int32.Parse(dt.Rows[0][MinDay]), Int32.Parse(dt.Rows[0][MinWeek]), Int32.Parse(dt.Rows[0][DayWeek]));
Note: Using Int32.Parse instead of Int32.TryParse assumes that the argument provided is a valid int at all times and does not give you a way to check if the casting has succeeded.
dt.Rows[0][ProjName] returns type object, and your method expects string. Even though you know it to be a string, it is not obvious to the compiler and must be specified explicitly using a cast, as you show in your last example, although just casting should be more efficient than converting unnecessarily:
Project = new Project((string)dt.Rows[0][ProjName], ...
Related
I have imported a very large CSV-table via Powershell into an array of objects.
Each object may look like this definition:
$test = [PsCustomObject]#{id='1';name='a'}
The problem is, that all types are 'string' and I need to work with the correct types later on in my code. For this I want to use an embedded C# code that converts the values of all object property into a string-array to add its values into a DataTable with correct type definitions.
I am now struggling on the C# part. Here is my code-sample which is not working.
How can I change the C# part to do the conversion of the object-values into an array?
# this code should convert the object-values into an array:
Add-Type -ReferencedAssemblies System.Data -TypeDefinition '
using System;
public class row {
public object[] row(object test) {
int id = Int32.Parse(test.id);
string name = test.name;
return (id, name);
}
}'
# this is the test-object:
$test = [PsCustomObject]#{id='1';name='a'}
# this is the final table with correct types:
$table = [System.Data.DataTable]::new()
[void]$table.Columns.Add('id', [int])
[void]$table.Columns.Add('name', [string])
# at the end it should work like this:
$row = [row]::new($test)
$table.Rows.Add($row)
I did some tests without C#, but this is very slow.
E.g. this simple loop (even without adding the data into the row) runs over 20 seconds:
$test = [PsCustomObject]#{id='1';name='a'}
foreach($x in 1..1MB) {
$row = foreach($i in $test.PsObject.Properties.Value){if ($i) {$i} else {$null}}
#[void]$table.rows.Add($row)
}
So in theory I need to do the same like in the last code-block but via embedded Csharp code.
How can I get this done in an efficient way?
Update #1:
Thanks to the input from Theo I could speed-up the conversion. I did not expect that this would be 5-times faster than just querying PsObject-properties. E.g. it turns out, that an 'else'-statement is slower than just assigning the var first. Here is the code for comparison:
$test = [PsCustomObject]#{id='1';name='a'}
foreach($x in 1..1MB) {
$id = $test.id
if ([string]::IsNullOrEmpty($id)){$id = $null}
$name = $test.name
$row = #($id, $name)
}
But it is still the slowest part in my overall code I am still looking for a smart C# solution. My idea is, that if there are any other properties for the input-object later, then I can just dynamically rebuild the C# code. That will not work for pure PS-code.
Update #2:
Based on the input from BACON I was able to solve the challenge with the C# code.
Here is my working implementation:
Add-Type -TypeDefinition '
using System;
public class test {
public string id {get;set;}
public string name {get;set;}
}
public static class DataParser {
public static string[] ParseToArray(test data) {
string id = data.id;
if (String.IsNullOrEmpty(id)) {id = null;};
string name = data.name;
return new string[] {id,name};
}
}'
# this is the test-object:
$test = [PsCustomObject]#{id='';name='a'}
$timer = [System.Diagnostics.Stopwatch]::StartNew()
foreach($x in 1..1MB) {
$row = [DataParser]::ParseToArray($test)
}
$timer.Stop()
$timer.Elapsed.TotalSeconds
What I did not expect is the runtime of this solution - it is way slower than the pure PowerShell version which I posted above. So my conclusion is "mission accomplished", but we missed the target. Which means that there is no real efficient way to convert object values into an array.
As a result of this, I will step away from importing CSV-data as objects and will focus on importing large data as XML via 'dataSet.readXML'. I only wish there would be a build-in option to directly import CSV-data as arrays or dataRows.
A pure PowerShell solution would be:
[int]$refInt = 0 # create an int as reference variable for TryParse()
foreach($item in $test) {
# get the int value or $null for the id property
$rowId = if ([int]::TryParse($item.id, [ref]$refInt)) { $refInt } else { $null }
# get the string value or $null for the name property
$rowName = $item.name.ToString() # added ToString() for good measure
if ([string]::IsNullOrWhiteSpace($rowName)) { $rowName = $null }
# add a new row to the table
$newRow = $table.NewRow()
$newRow["id"] = $rowId
$newRow["Name"] = $rowName
$null = $table.Rows.Add($newRow)
}
I'm not really into C#, but I think you need to use TryParse() there aswell in order to get either an int or a $null value. As for the name property you should also check this for NullOrWhiteSpace and use the ToString() method on it to make sure you get a valid string or $null.
You don't say how your C#-based attempt is "not working", but I can see some problems with...
using System;
public class row {
public object[] row(object test) {
int id = Int32.Parse(test.id);
string name = test.name;
return (id, name);
}
}
This is defining a class named row with an instance method (not a constructor) also named row. You don't define any constructors so the row class will have only a default, parameterless constructor. When you do...
$row = [row]::new($test)
...you are trying to invoke a row constructor overload that doesn't exist.
Further, the return type of row() is object[], yet (id, name) is a (value) tuple, not an array. Some conversion from the former to the latter is necessary for that to compile.
Invoking your Add-Type command I am reminded that...
Add-Type: (4,21): error CS0542: 'row': member names cannot be the same as their enclosing type
public object[] row(object test) {
^
...which explains itself, and...
Add-Type: (5,35): error CS1061: 'object' does not contain a definition for 'id' and no accessible extension method 'id' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
...which means that since the compile-time type of the test parameter is object, unless you cast it to a more specific type you'll only be able to access the members of the object class, which has no properties or fields. Since the run-time type of test will be PSCustomObject — which is a bit of a "magical" type — the typical (and slow) C# way of dynamically accessing the id and name properties with reflection won't work.
So, the problem is, basically, that despite having some superficial similarities, C# is very different than PowerShell and cannot be written as such. You could fix the issues above by stuffing the values into a more friendly type for C# to access, like this...
using System;
public static class DataParser {
public static object[] ParseToArray(Tuple<string, string> data) {
int id = int.Parse(data.Item1);
string name = data.Item2;
return new object[] { id, name };
}
}
Note that Tuple<,> generic type is not the same kind of tuple as linked previously; that one requires C# 7.0, so, for better compatibility, I'm not using it here. You could then call the above method like this...
$testTuple = [Tuple]::Create($test.id, $test.name)
$testAsArray = [DataParser]::ParseToArray($testTuple)
$table.Rows.Add($testAsArray)
Even simpler would be to eliminate the intermediate object and just pass the properties via parameters...
using System;
public static class DataParser {
public static object[] ParseToArray(string id, string name) {
return new object[] { int.Parse(id), name };
}
}
...and call it like this...
$testAsArray = [DataParser]::ParseToArray($test.id, $test.name)
$table.Rows.Add($testAsArray)
Seeing as how neither method implementation is doing much more than stuffing their inputs into an array, the next — and best — optimization is to recognize that the C# code isn't doing enough work to justify its usage and remove it entirely. Thus, we just create the array directly in PowerShell...
$testAsArray = [Int32]::Parse($test.id), $test.name
$table.Rows.Add($testAsArray)
Now, that simplifies your code, but it doesn't achieve the goal of making it faster. As I said, you need to do more work inside the C# method — like accepting all of the input records, parsing them as appropriate, and populating the DataTable — to make it worthwhile. For that, I think you'd need to show more of your code; specifically, how you go from CSV text to in-memory records, and if each record really is stored as a PSCustomObject (as returned by Import-Csv) or something else.
I've finally produced a minimal example that reproduces this error:
using System;
using Newtonsoft.Json;
class Program
{
public byte[] Foo(byte[] p) { return new byte[0]; }
public byte[] Foo(Guid? p) { return new byte[0]; }
static Guid? ToGuid(string s) { return s == null ? null : (Guid?)new Guid(s); }
void Bar()
{
dynamic d = JsonConvert.DeserializeObject<dynamic>("{}");
var id = d?.id?.ToString();
Foo(ToGuid(id));
}
static void Main(string[] args)
{
new Program().Bar();
}
}
Bizarrely it's crashing at runtime calling Foo when d.id is null (or not a string), saying it can't resolve which version of Foo to call (The call is ambiguous between the following methods or properties). Why on earth isn't this resolved at compile time though? The dynamic shouldn't make a difference that I can see, and in fact even more weirdly if I add an explicit cast "(Guid?)" before ToGuid... it works as expected, and likewise if I instead write it as:
Guid? id = ToGuid(d.id?.ToString());
Foo(id)
which actually makes more sense anyway. It also works fine if I change "var" to "string".
I noticed that the exception is initially thrown from "System.Linq.Expressions.dll" which is a bit odd. The full stack trace is basically:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: The call is ambiguous between the following methods or properties: 'FooService.Foo(byte[])' and 'FooService.Foo(System.Guid?)'
at CallSite.Target(Closure , CallSite , FooService , Object )
Exception source is "Anonymously Hosted DynamicMethods Assembly"
Now that we have the var variant I can reproduce the issue. And the issue is nulls. You may think that the return type of ToGuid must be a Guid? because you're assuming knowledge that the compiler doesn't work with. So far as it's concerned, in Bar it's looking at an id with type dynamic1. This means that it's going to assume that whatever ToGuid returns it'll store in a dynamic temp variable.
In this case, it returns null and under the covers, dynamic is just object. At that point, we've lost any compile time type information about the return type from ToGuid. If it wasn't null, before it resolves Foo it would effectively call GetType on the instance. But that's not possible here. It has null and two equally good/bad candidates it could call with a null reference. It's as if you'd written Foo(null); (which generates the equivalent error at compile time).
Inserting an explicit cast - Foo((Guid?)ToGuid(id)); gives the runtime sufficient information at the callsite to be able to unambiguously select the correct overload of Foo you wanted it to choose.
1Bear in mind that whatever the type of the id property on d is, it may have a ToString method that shadows the one from object. It cannot assume it returns a string so id is dynamic too.
I 'm new to C#. Is there any way to create a function that can change the datatype of a variable to an another datatype and return the changed value.
I don't want to use the built-in Convert class.
For example :
Let's assume that ChangeDataType is the name of the function. It should also be possible to do the same thing with all datatypes.
int a = 5;
float b = ChangeDataType(a, "float"); // 5.0
Thanks.
Please note that you cannot change data type of already declared variable. But you can create new variable of desired type and then typecast old variable into new one. If this type casting fails, then invalidcastexception is thrown.
Below are different kinds of typecasting options:
1. Implicit Type casting
In this you don't need to use any syntax.
For ex:
int i = 10;
float a = i;
2. Explicit Type casting
You need to specify the type in which you want to convert to. For ex:
class Test
{
static void Main()
{
double x = 1234.7;
int a;
// Cast double to int.
a = (int)x;
System.Console.WriteLine(a);
}
}
// Output: 1234
3. User Defined Casts
Using implict and explicit keywords in c#.
Please refer
4. Helper Classes
There are many classes which provides method to convert data types.
One of the important class is - Convert.
This class provides a lot of methods. Convert.ToString, Convert.ToInt32, etc.
Another example is TryParse methods in int, long, double, DateTime, etc classes. Please refer
I set the following property:
public Object Value
{
get
{
return AdministrationSettings.Default[settingCode];
}
set
{
AdministrationSettings.Default[settingCode] = value; // <<< Error occurs here
this.RaisePropertyChanged(() => this.Value);
}
}
This property provides the link between the fields of my interface and those of the object AdministrationSettings
AdministrationSettigs represents Settings class .net (having an extension .Settings)
Inside I defined within the properties here is an example:
When I made the entered data in a field in my interface, here display this interface:
the program stops at the instruction of line 9, and generates this error:
The settings property "ExclusionZone" is of a non-compatible type, here the code
the ExclusionZone is one parameter which defined in the .Settings File. its type is double. It is also in the same file (. Settings) they set other parameters, there are those who are of type string, double, Boolean
the problem is only in the Set, for the Get get it's right
I hope there will be someone who can help me
Thanks
private int value = Convert.ToInt32(Properties.Settings.Default.Setting);
Properties.Settings.Default["Setting"] = value + 1;
This crashed for me but when I changed to value + 1.tostring(); It worked. Ofcourse because my "setting" is of type string. So check that ur value is of the right type :)
First of all you might want to change the manner in which you are trying to access the application level properties that you defined.
As alexander points out try: Properties.Settings.Default["PropertyVariable"] instead of AdministrationSettings.Default["PropertyVariable"]
Secondly you have defined three properties namely: (1) ExclusionZone, (2)AlertZone (3)ExcessiveSpeed but you are trying to access 'settingCode' which is not defined.
Thirdly you are missing inverted commas.
Once you sort these three things, make sure you cast 'value' to the correct data type.
I use ProfileBase to get and maintain extra settings in a user profile. The ProfileBase object that you get from the ProfileBase.Create() function seems to return something like a dictionary of string-to-string. Therefore, when I make changes to values and save them, I have to cast them to string, and then call the Save() function on the ProfileBase object. Here's me saving a bool flag, but first converting it to an int (1 or 0) and then saving that as a string...
ProfileBase pb = ProfileBase.Create(userNew.UserName, true);
if (pb != null)
{
int iCreateFlag = createDefaultDummyAccounts ? 1 : 0;
pb["CREATEDEFAULTDUMMYACCOUNTS"] = iCreateFlag.ToString();
pb.Save();
}
If I do not cast iCreateFlag to string, and I try to save the iCreateFlag as an int, I get "The settings property is of a non compatible type", even though my column is defined in the database as INT. Hope this helps anyone else out with this problem.
I have a DetailsView, I need get the value specifield in DataKeyNames "UserId" (a Guid Field) and add it to an Object Guid.
At the moment I am using this code:
String myUserId = (String)uxAuthorListDetailsView.DataKey["UserId"].ToString();
but I would need something like:
Guid myUserGuid = (Guid)uxAuthorListDetailsView.DataKey["UserId"].ToString();
But does not work I get error Error
Cannot convert type 'string' to 'System.Guid'
What could be the problem? Thanks guys for your support!
Well, the problem is that you're calling ToString() and then trying to cast that string to a Guid, when there's no such conversion available. Possible alternatives:
Cast instead of calling ToString(), if the original value is really a Guid:
Guid guid = (Guid)uxAuthorListDetailsView.DataKey["UserId"];
Parse the string instead:
Guid guid = new Guid(uxAuthorListDetailsView.DataKey["UserId"].ToString());
If this is user-entered data at all, or there's any other reason why the value is half-expected to be incorrect, and if you're using .NET 4, you might want to use Guid.TryParse instead, which will allow you to handle failure without an exception.
var myUserGuid = new Guid(uxAuthorListDetailsView.DataKey["UserId"].ToString());
Check under debug: what object is uxAuthorListDetailsView.DataKey["UserId"]
I guess this must be already the Guid; and conversion is not needed
Assuming uxAuthorListDetailsView.DataKey["UserId"] stores a valid Guid
try this
Guid myUserGuid = (Guid)uxAuthorListDetailsView.DataKey["UserId"]
remove ToString method