I was able to create an array of a struct I created, but I'm having trouble doing the same for an array of a class. I'm (faintly) aware that this probably isn't the best way to do this, but I'd appreciate help in figuring out what's going on.
I'm about 2 days into learning C#, and I'm navigating away from MS Office-VBA, if that gives you an idea of what I'm into. Anyway, I'm following an online reference, and along the way trying to play with what I've learned so far. This problem has come about as a result of my playing.
First, let me describe what I've done with the struct, and the array of that struct, with some code snippets.
I've been able to create a struct, called Machines...
// play with structs
struct Machines
{
// vars for struct
private string model, SN;
private int hours;
// assign values
public void AssignValues(string model_in, string SN_in, int hours_in)
{
model = model_in;
SN = SN_in;
hours = hours_in;
}
// display values
public void DisplayValues()
{
Console.WriteLine("Model: {0}", model);
Console.WriteLine("SN: {0}", SN);
Console.WriteLine("Hours: {0}", hours);
}
};
... things seem to work just fine:
public static void Main()
{
// play with structures
Machines machine1 = new Machines();
machine1.AssignValues("AA", "ABC01234", 34760);
machine1.DisplayValues();
Output is:
Model: AA
SN: ABC01234
Hours: 34760
Then, I can create an array of the struct, and things continue to go well:
// play with structures and arrays
// declare, create new instance
Machines [] MyArr = new Machines[10];
MyArr[0].AssignValues("AA", "ABC01235", 43000);
MyArr[0].DisplayValues();
But, when I attempt to do the same with a class, it's a different story. What's going on?
public class ArmstrongMachine
{
// vars for struct
private string model, SN;
private int hours;
// assign values
public void AssignValues(string model_in, string SN_in, int hours_in)
{
model = model_in;
SN = SN_in;
hours = hours_in;
}
// display values
public void DisplayValues()
{
Console.WriteLine("Model: {0}", model);
Console.WriteLine("SN: {0}", SN);
Console.WriteLine("Hours: {0}", hours);
}
};
...
// play with classes
ArmstrongMachine [] MyMachines = new ArmstrongMachine[10];
MyMachines[0].AssignValues("AA", "ABC01236", 51000);
MyMachines[0].DisplayValues();
The issue seems to begin with MyMachines[0].AssignValues.... If I comment out that line and the following, there are no problems (other than the warning that I've created a variable I'm not using).
Any ideas?
Also, please be aware that this is being compiled online.
A class gives you a reference type in C#.
The array thus holds references to objects, and not the objects themselves.
The array initially contains nothing, all zeroes, which means all the references will be null.
You need to initialize each element of the array to hold an object reference:
// play with classes
ArmstrongMachine [] MyMachines = new ArmstrongMachine[10];
MyMachines[0] = new ArmstrongMachine();
MyMachines[0].AssignValues("AA", "ABC01236", 51000);
MyMachines[0].DisplayValues();
MyMachines[1] = new ArmstrongMachine();
MyMachines[2] = new ArmstrongMachine();
...
MyMachines[9] = new ArmstrongMachine();
If the array holds value types, like the structs, then the array holds the struct values themselves, thus it works with the structs, and not with the objects.
Also note that you should emphatically not use mutable structs (structs you can change). There's tons of things that can go wrong and bite you in ... so you should not use them. Go with classes in this case.
Here's a video on the subject of mutable structs: Evil Structs, by Jon Skeet.
In simple words: A struct is only a way the memory is structured whereas a class is a real object. For the second example, you need to create instances of the class before you can make calls to it, because it only points to a memory location (which may be unassigned = a null reference):
ArmstrongMachine [] MyMachines = new ArmstrongMachine[10];
MyMachines[0] = new ArmstrongMachine();
MyMachines[0].AssignValues("AA", "ABC01236", 51000);
MyMachines[0].DisplayValues();
Related
The following code doesn't update the copy of a inside the array.
int a = null;
int[] numbers = new int[1];
numbers[0] = a;
a = 5; // 5
Console.WriteLine(numbers[0]); // null
Got a programming task requiring to set-up a structure of locations linked by portals between them which isn't possible by just listing the required connections. I'll get references to null that stay null even if I fill an entity later in the code.
Looking for keywords or techniques which might solve my issue.
You could have reference types instead of value types inside array, therefore updating the value of the inner object will also affect the array.
var tab = new MyClass[1];
var obj = new MyClass(5);
tab[0] = obj;
Console.WriteLine(tab[0].Value); // 5
tab[0].Value = 10;
Console.WriteLine(tab[0].Value); // 10
obj.Value = 15;
Console.WriteLine(tab[0].Value); // 15
public class MyClass
{
public MyClass(int value)
{
Value = value;
}
public int Value { get; set; }
}
integers is a value type, as such the actual value is copied. So there is never any 'instance' in your example code, only copies of the value.
You should probably wrap your value in a class, since classes are a reference type to get your desired behavior. This might be useful when you need to share some mutable between multiple components. You can also add an event that is raised whenever the value is changed to let any component that needs the value know that it might need to update something. For example:
public class MyChangeable<T>
{
private T value;
public MyChangeable(T value) => this.value = value;
public T Value
{
get => value;
set
{
this.value = value;
OnChanged(this, value);
}
}
public event EventHandler<T> OnChanged;
}
There is also ref return and ref locals that could do something like your example, but this is mostly intended to get better performance by avoiding copies of large structs, it is not as useful if you want to share values between components.
Arrays are reference types
var a = new int[1];
var numbers = new [] { a };
a[0] = 5;
Console.WriteLine(numbers[0][0]);
You just have to remember that you're one level deeper than you wanted to be/you need to stick a [0] on everything you wouldn't have stuck it on before. It's a bit of a hack, and I'd probably make a class for it like other answers recommend, but stuffing a value type in an array of size 1 can be a useful technique to quickly get reference type behavior out of a value type
ref locals may help you in your task, even if they have strict limitations due to the lifetime of the involved objects, so they could be not applicable as a general solution.
A small example based on your question can be as follows:
int[] array = new int[1];
ref int elem = ref array[0];
elem = 5;
Console.WriteLine(array[0]); // 5
This works not only with value types (including nullable types) but also with reference types.
I have a little question about arrays of struct in C#: lets say I have a struct Foo:
struct Foo
{
public string S;
public int X;
...
...
}
and I have an array of Foo:
Foo[] arr = ...
In one method, I use arr[i] quite often, so I'd like to keep it in a local variable (the expression for i is also a little long):
var f = arr[i]
Now, my problem is that I know structs are value type, which means assignments like this cause a copy. The struct is a little big (7 strings and a bool), so I'd prefer to avoid copying in this case.
If I am not mistaken, the only way to access the struct's fields without copying the struct is to use the array directly: arr[i].S or arr[i].X, but this quickly becomes annoying to read. I'd really like to keep the array element in a local variable, but I don't want to waste performance by copying it into the variable.
Is there a way to make something like a reference variable (similar to C++) to avoid copying? If not, than I'm curious if it's something the compiler optimizes?
How should I deal with this element? Can I put it in a local variable without copying or do I have to access it through the array to avoid copying?
Thanks in advance.
You can do this in C# 7 and later using ref local variables:
using System;
public struct LargeStruct
{
public string Text;
public int Number;
}
class Test
{
static void Main()
{
LargeStruct[] array = new LargeStruct[5];
// elementRef isn't a copy of the array value -
// it's really the variable in the array
ref LargeStruct elementRef = ref array[2];
elementRef.Text = "Hello";
Console.WriteLine(array[2].Text); // Prints hello
}
}
Of course, I'd normally recommend avoiding:
Large structs
Mutable structs
Public fields (although if it's mutable, doing that via public fields is probably best)
... but I acknowledge there are always exceptions.
I'm trying to use reflection (ultimately on unknown at compile time) object which include struct. I've got as far as TypedReference.MakeTypedReference but I've hit a wall.
Here's my Class and Struct
public class MyObject
{
public int Id;
public Money Amount;
}
public struct Money
{
public int Vaule;
public string Code;
}
And here is how I am trying to set "Code" of "Amount" in MyObject using reflection. As I mention above, I'm looking for a solution which does not know about these types at compile time (that would be too easy!)
Here's the code I have so far (I've used [0], [1] to make the code simpler)
var obj = new MyObject() { Id = 1 };
obj.Amount.Vaule = 10;
obj.Amount.Code = "ABC";
FieldInfo[] objFields = obj.GetType().GetFields();
FieldInfo[] moneyFields = objFields[1].GetValue(obj).GetType().GetFields();
List<FieldInfo> fields = new List<FieldInfo>() { objFields[1] };
fields.AddRange( moneyFields );
TypedReference typeRef = TypedReference.MakeTypedReference(
objFields[1].GetValue( obj ), fields.ToArray() );
moneyFields[1].SetValueDirect( typeRef, "XXX" );
The TypedReference.MakeTypedReference blows up with; "FieldInfo does not match the target Type." Likewise if I just pass objFields[1]. And if pass just moneyFields I get "TypedReferences cannot be redefined as primitives."
Why? Let's say I'm creating Random test fixtures and want to populate class fields with random data :)
Frankly, there's no need whatsoever for TypedReference here - just a boxed struct should work fine:
var amountField = obj.GetType().GetField("Amount");
object money = amountField.GetValue(obj);
var codeField = money.GetType().GetField("Code");
codeField.SetValue(money, "XXX");
amountField.SetValue(obj, money);
However! I will advise you of a few things:
public fields instead of properties are not usually a good idea; that will often bite you later
mutable structs (i.e. structs that can be changed after creation) are almost never a good idea, and will bite even more often, and bite harder
combining mutable structs and public fields compounds it, but making it very problematic to change later
I have a problem using a class of made of structures.
Here's the basic definition:
using System;
struct Real
{
public double real;
public Real(double real)
{
this.real = real;
}
}
class Record
{
public Real r;
public Record(double r)
{
this.r = new Real(r);
}
public void Test(double origval, double newval)
{
if (this.r.real == newval)
Console.WriteLine("r = newval-test passed\n");
else if (this.r.real == origval)
Console.WriteLine("r = origval-test failed\n");
else
Console.WriteLine("r = neither-test failed\n");
}
}
When I create a non-dynamic (static?) Record, setting the Real works.
When I create a dynamic Record, setting the real doesn't work.
When I create a dynamic Record, replacing the real works.
And here's the test program
class Program
{
static void Main(string[] args)
{
double origval = 8.0;
double newval = 5.0;
// THIS WORKS - create fixed type Record, print, change value, print
Record record1 = new Record(origval);
record1.r.real = newval; // change value ***
record1.Test(origval, newval);
// THIS DOESN'T WORK. change value is not making any change!
dynamic dynrecord2 = new Record(origval);
dynrecord2.r.real = newval; // change value
dynrecord2.Test(origval, newval);
// THIS WORKS - create dynamic type Record, print, change value, print
dynamic dynrecord3 = new Record(origval);
dynamic r = dynrecord3.r; // copy out value
r.real = newval; // change copy
dynrecord3.r = r; // copy in modified value
dynrecord3.Test(origval, newval);
}
}
And here's the output:
r = newval-test passed
r = origval-test failed
r = newval-test passed
When I change the struct Real to class Real, all three cases work.
So what's going on?
Thanks,
Max
dynamic is really a fancy word for object as far as the core CLI is concerned, so you are mutating a boxed copy. This is prone to craziness. Mutating a struct in the first place is really, really prone to error. I would simply make the struct immutable - otherwise you are going to get this over and over.
I dug a little deeper into this problem. Here's an answer from Mads Torgersen of Microsoft.
From Mads:
This is a little unfortunate but by design. In
dynrecord2.r.real = newval; // change value
The value of dynrecord2.r gets boxed, which means copied into its own heap object. That copy is the one getting modified, not the original that you subsequently test.
This is a consequence of the very “local” way in which C# dynamic works. Think about a statement like the above – there are two fundamental ways that we could attack that:
1) Realize at compile time that something dynamic is going on, and essentially move the whole statement to be bound at runtime
2) Bind individual operations at runtime when their constituents are dynamic, returning something dynamic that may in turn cause things to be bound at runtime
In C# we went with the latter, which is nicely compositional, and makes it easy to describe dynamic in terms of the type system, but has some drawbacks – such as boxing of resulting value types for instance.
So what you are seeing is a result of this design choice.
I took another look at the MSIL. It essentially takes
dynrecord2.r.real = newval;
and turns it into:
Real temp = dynrecord2.r;
temp.real = newval;
If dynrecord2.r is a class, it just copies the handle so the change affects the internal field. If dynrecord2.r is a struct, a copy is made, and the change doesn't affect the original.
I'll leave it up to the reader to decide if this is a bug or a feature.
Max
Make your struct immutable and you won't have problems.
struct Real
{
private double real;
public double Real{get{return real;}}
public Real(double real)
{
this.real = real;
}
}
Mutable structs can be useful in native interop or some high performance scenarios, but then you better know what you're doing.
From a method, I can pass a struct which contains an array of integers, and change the values in the array. I am not sure I understand fully why I can do this. Can someone please explain why I can change the values stored in the int[]?
private void DoIt(){
SearchInfo a = new SearchInfo();
a.Index = 1;
a.Map = new int[] { 1 };
SearchInfo b = new SearchInfo();
b.Index = 1;
b.Map = new int[] { 1 };
ModifyA(a);
ModifyB(ref b);
Debug.Assert(a.Index == 1);
Debug.Assert(a.Map[0] == 1, "why did this change?");
Debug.Assert(b.Index == 99);
Debug.Assert(b.Map[0] == 99);
}
void ModifyA(SearchInfo a) {
a.Index = 99;
a.Map[0] = 99;
}
void ModifyB(ref SearchInfo b) {
b.Index = 99;
b.Map[0] = 99;
}
struct SearchInfo {
public int[] Map;
public int Index;
}
In C#, references are passed by value. An array is not copied when passed to method or when stored in an instance of another class. - a reference to the array is passed. This means a method which recieves a reference to an array (either directly or as part of another object) can modify the elements of that array.
Unlike languages like C++, you cannot declare "immutable" arrays in C# - you can however uses classes like List which have readonly wrappers available to prevent modification to the collection.
From a method, I can pass a struct which contains an array of integers, and change the values in the array. I am not sure I understand fully why I can do this.
An array is defined as a collection of variables.
Variables, by definition, can be changed. That is why we call them "variables".
Therefore when you pass an array, you can change the contents; the contents of an array are variables.
Why can I change a struct’s int[] property without specifying “ref”?
Remember, as we discussed before in a different question, you use ref to make an alias to a variable. That is what "ref" is for -- making aliases to variables. (It is unfortunate that the keyword is the confusing "ref" -- it probably would have been more clear to make it "alias".)
From MSDN:
Do not return an internal instance of an array. This allows calling code to change the array. The following example demonstrates how the array badChars can be changed by any code that accesses the Path property even though the property does not implement the set accessor.
using System;
using System.Collections;
public class ExampleClass
{
public sealed class Path
{
private Path(){}
private static char[] badChars = {'\"', '<', '>'};
public static char[] GetInvalidPathChars()
{
return badChars;
}
}
public static void Main()
{
// The following code displays the elements of the
// array as expected.
foreach(char c in Path.GetInvalidPathChars())
{
Console.Write(c);
}
Console.WriteLine();
// The following code sets all the values to A.
Path.GetInvalidPathChars()[0] = 'A';
Path.GetInvalidPathChars()[1] = 'A';
Path.GetInvalidPathChars()[2] = 'A';
// The following code displays the elements of the array to the
// console. Note that the values have changed.
foreach(char c in Path.GetInvalidPathChars())
{
Console.Write(c);
}
}
}
You cannot correct the problem in the preceding example by making the badChars array readonly (ReadOnly in Visual Basic). You can clone the badChars array and return the copy, but this has significant performance implications.
Although your SearchInfo struct is a value type, the .Map field is holding a reference, because Array is a reference type. Think of this reference as the address pointing to the memory location where the array resides.
When you pass an instance of SearchInfo to a method, as you know, the SearchInfo gets copied. And the copy naturally contains the very same address pointing to the very same array.
In other words, copying the struct doesn't make a copy of the array, it just makes a copy of the pointer.
Well, it is passed by reference anyway, like all reference types in C#.
Neither C# nor CLR support constness, unfortunately, so the platform doesn't really know if you are allowed to change it or not. So, it has the reference, it may use it to change the value, and there's nothing to stop it from doing so.
You may see it as a language design bug, btw. It is unexpected for the user.