Trying to Understanding structs. Reference by a List<> - c#

I have the following code:
public void Start()
{
List<StructCustomer> listCustomer = new List<StructCustomer>();
listCustomer.Add(
new StructCustomer { ID = 0, name = "Any Default Name", birthday = DateTime.Now });
DoSomethingWithStructList(listCustomer);
StructCustomer customer = listCustomer.First();
Console.WriteLine("ID = {0}, Name = {1}", customer.ID, customer.name); // Writes ID = 0, Name = "Any Default Name"
}
public void DoSomethingWithStructList(List<StructCustomer> listStructs)
{
StructCustomer test = listStructs.First();
test.ID = 2;
test.name = "Edited by method";
Console.WriteLine("ID = {0}, Name = {1}", test.ID, test.name); // Writes "ID = 2, Name = Edited by method"
}
public struct StructCustomer
{
public int ID { get; set; }
public string name { get; set; }
public DateTime birthday { get; set; }
}
As you can notice, the variable List is a reference to a List of Customer. Shouldnt the value be edited in the StructCustomer Variable in the List?
I know Structs are value and not reference types, but i am boxing it in a List!

Well, when you do this:
StructCustomer test = listStructs.First();
test.ID = 2;
test.name = "Edited by method";
Console.WriteLine("ID = {0}, Name = {1}", test.ID, test.name);
you are actualy creating a copy of the first struct in the listStructs, so, you'll change the values of the copy, not the real one. Try doing this instead - it should works:
listStructs.First().ID = 2;
listStructs.First().name = "Edited by method";
So, thats it ;)
OBS: This approach is not recomended by CPU usage, but, its a way out =)

Structs are value types, and as such, when you retrieve them from the list, as in your example, what you are retrieving is a copy of its value. Which you then modify. This does not change anything in the original that is contained in the list.
If you want to make changes to the element in the list, do it like this:
listStructs[0].ID = 2;
listStrings[0].name = "Edited by method";

Creating a list of a structure type will cause each item of the list to encapsulate all of the fields within the structure. The list's indexed 'get' method will copy all of the fields associated with a list item to the corresponding fields of the return value; the indexed 'put' will copy all of the fields from the passed-in item to the corresponding fields associated with the appropriate list item. Note that neither the 'get' nor the 'put' creates any attachment between the item in the list and the item which is read or written; future changes to one will not affect the other.
For many kinds of programs, this kind of detachment will sometimes be desirable and sometimes not. To help facilitate such cases, I would suggest creating a type like:
class SimpleHolder<T> { public T Value; /* A field, not a property! */ }
and then using a List<SimpleHolder<StructCustomer>>.
Your class should create every SimpleHolder<StructCustomer> instance itself, and never expose references to any of those instances to outside code. If you want to have a method, e.g. return item 0, use:
StructCustomer FirstCustomer()
{
return listStructHolders[0].Value;
}
To store a passed-in value:
void AddCustomer(StructCustomer newCustomer)
{
var temp = new SimpleHolder<StructCustomer>();
temp.Value = newCustomer;
listStructHolders.Add(temp);
}
To modify the name of customer 3:
listStructHolder[3].Value.name = "Fred";
Using a simple "exposed-field holder" class will make it easy to combine the advantages of structure types and mutable classes.

The List contains value types, so it returns a values when you ask it for an item. Try making a List and you'll see the same behavior.
As a result, you need to make assignments directly to the list item inside the list. You can have behavior semantically closer to what you are trying to do by iterating over the list in a foreach loop.

Related

C#: Why does my string return object type and not the value it contains?

I am looping through a List, and trying to instantiate one of the properties as a string, but it returns the type:
{Namespace.Collection}
If I put a break-point, I can see that it holds the value I need.
How can I make it return the value and not the type?
foreach (var PropertyName in ListName) {
string n = PropertyName.ToString();
}
UPDATE (added more of my code, as well as an attempt of implementing suggested solutions):
foreach (DataRow dr in ds.Tables[0].Rows) {
//PaidTrips is my ObservableCollection instance.
//PaidTrip is my holder class which it has been bound to.
PaidTrips.Add(new PaidTrip {
LicenseHolderID = dr[0].ToString(),
// adding more properties
});
List<PaidTrip> theseTrips = PaidTrips
.GroupBy(p => new { p.LicenseHolderID })
.Select(g => g.First())
.ToList();
foreach (PaidTrip PaidTrips in theseTrips) {
foreach (var LicenseHolderID in PaidTrips.GetType().GetProperties()) {
string n = LicenseHolderID.GetValue(PaidTrips).ToString();
// code to create PDF
}
gfx.DrawString(n, new XFont("Arial", 40, XFontStyle.Bold), ridelGreen, new XPoint(40, 350));
This is what I do with string n. But when the PDF is created, the string output is System.Action1[System.Action]`
What am I doing wrong?
You need to loop through the Property Types in your custom class, after looping through the list. First we need an additional loop - to loop through each ClassName Object in ListName list.
foreach (ClassName myObj in ListName)
{
foreach (var PropertyName in myObj.GetType().GetProperties())
{
string n = PropertyName.GetValue(myObj).ToString();
}
}
Then we need to loop the actual properties of the current loop ClassName object.
Then you pass the argument .GetValue (as you are now looping through the properties - the actual properties assigned, not the definition of properties).
After, you still need to specify what object you want the value of. So by passing myObj, you are specifying the ClassName->Property of the current loop of ListName.
EDIT:
List<Notes> myNotesNow = new List<Notes>();
myNotesNow.Add(new Notes
{
note1 = "Valuye"
// adding more properties
});
List<Notes> theseTrips = myNotesNow;
foreach (Notes PaidTrips in theseTrips)
{
foreach (var myVariable in PaidTrips.GetType().GetProperties())
{
string n = myVariable.GetValue(PaidTrips).ToString();
string forBreakPoint = "";
// code to create PDF
}
}
For your question - I guess that ListName is not of type string and so you get the expected behavior (see: https://learn.microsoft.com/en-us/dotnet/api/system.object.tostring?view=net-5.0#the-default-objecttostring-method)
In that case you can override the ToString() function of that object to return whatever you need like this:
public override string ToString()
{
return "whatever you want including class properties";
}
On another note, the general approach to variable naming in C# is camelCase and starts with lower case so I suggest to name your variables propertName instead of PropertyName and listName instead of ListName.
Moreover - naming variables for how they are implemented (ListName) is not a best practice as it binds them together, not allowing flexibility in case implementation changes (that comment is true only if it makes sense, as I dont see all the code)
Cheers

Another class property call from base class list

I have a two class properdata and pprosecnddata both classes having property
I want to access product property from properdata class list object. How is it possible,below is my sample code
pupilc class ProperData
{
public string code{get;set;}
public List<ProSecndData>Secnd{get;set;}
}
public class ProSecndData
{
public string product{get;set;}
}
I am trying to call property like that
class Program
{
static void Main(string[] args)
{
ProperData.Secnd.Product = "Hello";
}
}
you cannot directly access property of Secnd as it is a list
you need to iterate or select the index of the List<Secnd>
you must initialize Secnd first and Secnd should have items in the list
properData.Secnd = new List<ProSecndData>();
so it can be access via
foreach(var second in properData.Secnd)
{
second.product = "hello";
}
//or
for(var i = 0; i < proderData.Secnd.Count(); i++)
{
properData.Secnd[i].product = "hello";
}
//or
var index = //0-length of list;
properData.Secnd[index].product = "hello";
if you want to have items first then add first on your Secnd List
properData.Secnd = new List<ProSecndData>();
properData.Secnd.Add(new ProSecndData{ product = "hello"});
then you now can iterate the list by using methods above
You are trying to access list as a single object, which is not possible.
you need to create single instance of your list class and then you can add string in that single instance.
properData.Secnd = new List<ProSecndData>();
ProSecndData proSecndData = new ProSecndData();
proSecndData.product = "Hello";
properData.Secnd.Add(proSecndData);
Actually I know the answer already, you have not created a constructor to initialise your List.
I'm guessing you get a object null ref error?
Create the constructor to initialise your list and it should be fine.
But in future, please post the error message (not the whole stack, just the actual error) as well as all the code required to repeat the issue. Otherwise you run the risk of getting your question deleted
(It should be deleted anyway because it could be considered a "what is a null ref err?" question).
Also you are accessing an item in a list like the list is that item (should be more like: ProperData.Secnd.elementAt(0).product, please also note the capitalisation of 'product' in the model vs your code.

How to modify values of an object stored in a list

I have a class that is instantiated at the beginning of each iteration of a loop. Inside the loop, it's attributes need to be populated with the row values of a table returned by a stored procedure. As I have to iterate through each column of every row, in order to know which attribute of the class needs to be assigned a value and when, I have a dictionary that maps the column names to an index. This index refers to a position in a list that stores an attribute of an instance of the class:
while (reader.Read() && reader.HasRows)
{
Subscription subscription = new Subscription();
List<string> subscrData = new List<string>
{
subscription.attr1,
subscription.attr2,
subscription.attr3,
subscription.attr4
}
Dictionary<string, int> columnDict = new Dictionary<string, int>
{
{"attr1": 0},
{"attr2":1},
{"attr3":2},
{"attr4":3}
}
foreach (string colName in columnDict.Keys)
{
if (reader.GetSchemaTable().Columns[colName] == null)
subscrData[columnDict[colName]] = "null";
else
{
subscrData[columnDict[colName]] = reader[colName].ToString();
nullsReturned = false;
}
}
I'm probably coming at this from more of a C++ approach as with that you could store references to the class instance an modify its attributes, but this doesn't work with C# because lists store the values.
How can I restructure this code so that I can modify the actual attributes of the class instance while still being able to check if each column returned from the stored procedure is not null?
You don't need the list for this case. You either want to add a method like setAttribute(string attributeName) to your class (and within it build a switch/case to modify the given attribute); or, use reflection to change an instance field given its name.
I agree with Hasan. But just for your information: to implement your approach you could make use of Lambda expressions to keep track of the references to your properties (= the attributes).
Something like this would work:
Subscription subscription = new Subscription();
List<Expression<Func<Subscription, string>>> subscrData = new List<Expression<Func<Subscription, string>>>
{
a => a.attr1,
a => a.attr2,
a => a.attr3,
a => a.attr4,
};
//E.g. To update attribute 3 you can do this:
var prop = (PropertyInfo)((MemberExpression)subscrData[2].Body).Member;
prop.SetValue(subscription, "test string", null);

Unwanted list modification

I have an issue with trying to modify a list of Transactions within a foreach. I have created copies of the list passed into my method, made it read only, and yet when I try to change a value within any of the lists it changes that value within them all. Some type of memory link? I am unsure how to resolve this issue.
My program starts by declaring a class called Transaction (which is a generic class having Name, Value, Formatting), then I have subClasses :Transaction. I create a TransList (from public class TransList : IEnumerable) which has an object instance of each subClass. So a TransList would include a class named TranID, Amount, OrderID, Time, CardType, Comment1, Comment2. Each value of these subclasses could be string, decimal, DateTime. After that a list of TransParts is created it is then put into a larger list called processTrans.
So Comment2 is the element with a payment citation number and if there is more than one number in there I want to separate that into more than one TransList add those new TransLists to processTrans and remove the non-separated one. From my code below, having tried every strategy, run-time modification occurs to not only the intended processTrans but also to tempProcessTrans, addOn, tran, tranPart.
If processTrans that is passed into method looks like this in the debugger locals
processTrans [0] _Items TranID.Value = SD234DF and Comment2 = adf;wer;
Then the output should be
processTrans [0] _Items TranID.Value = SD234DF-1 and Comment2.Value=adf
processTrans [1] _Items TranID.Value = SD234DF-2 and Comment2.Value=wer
I currently get
processTrans [0] _Items TranID.Value = SD234DF-1-2 and Comment2.Value=wer
processTrans [1] _Items TranID.Value = SD234DF-1-2 and Comment2.Value=wer
public static List<TransList> SeperateMultiCitations(List<TransList> processTrans) //change TransList seperating Multiple Citations
{
List<int> indexes=new List<int>();
IList<TransList> tempProcessTrans = processTrans.AsReadOnly(); //this didn't help
List<TransList> addOn= new List<TransList>(); //copy list didn't stop from changes to occur in processTrans at same time
foreach (TransList tran in tempProcessTrans.ToList())
{
TransList copyTransList = tran;
foreach (Transaction tranPart in tran.OfType<Comment2>())
{
if (new Regex(";.+;").IsMatch((string)tranPart.Value, 0))
{
string[] citations = Regex.Split((string)tranPart.Value, ";").Where(s => s != String.Empty).ToArray();
int citNumb = 1;
indexes.Add(tempProcessTrans.IndexOf(tran));
foreach (string singleCitation in citations)
{
addOn.Add(ChangeTrans(tran, singleCitation, citNumb++)); when this line runs changes occur to all lists as well as trans, tranPart
}
break;
}
}
}
foreach (int index in indexes.OrderByDescending(x => x))
{
processTrans.RemoveAt(index);
}
processTrans.AddRange(addOn);
return processTrans;
}
public static TransList ChangeTrans(TransList copyTransList, string singleCitation, int citNumb) //add ConFee
{
foreach (Transaction temp in copyTransList.OfType<TranID>())
{
temp.Value += "-" + citNumb;
}
foreach(Transaction temp in copyTransList.OfType<Comment2>())
{
temp.Value = singleCitation;
}
foreach (Transaction temp in copyTransList.OfType<Amount>())
{
//temp.Value = DboGrab(temp);
//temp.Value = amount;
}
return copyTransList;
}
public class Transaction : TranInterface
{
public string Name;
public object Value;
public string Formating;
public Transaction(string name, object value, string formating)
{
Name = name;
Value = value;
Formating = formating;
}
}
class TranID : Transaction
{
public TranID(string Name, string Value, string Formating) : base("Transaction ID", Value, "#") { }
}
public class TransList : IEnumerable<Transaction> //not to add all the lengthy parts here but this just allows for adding the parts and iterating through them in the foreach statements
{}
The behavior you're seeing is an inherent feature of reference types. When you call the ChangeTrans() method, the reference returned by that method is exactly the same as the one you passed in, which is that original value tran. Within the inner loop, the value of tran never changes, so on each iteration of the loop, you are modifying the same object over and over, adding it to your addOn list with each iteration.
This has two undesirable effects:
There is no difference between each element in the addOn list. They are all identical, referencing the same single object.
Any modification of any single element in the addOn list, or via the original reference to that single object, is visible via every other reference to that same single object. I.e. via all of the other elements in the list, and even that original reference in the tran variable (and of course, the copyTranList variable, which was assigned to the value of tran).
Without a more complete code example, it's not possible to know for sure what the best solution would be. However, one naïve solution would be to simply change your ChangeTrans() method so that it is responsible for making the new copy:
public static TransList ChangeTrans(
TransList copyTransList, string singleCitation, int citNumb) //add ConFee
{
TransList newTransList = new TransList();
foreach (Transaction temp in copyTransList.OfType<TranID>())
{
Transaction newTransaction = new TranID();
newTransaction.Value = temp.Value + "-" + citNumb;
newTransList.Add(newTransaction);
}
foreach(Transaction temp in copyTransList.OfType<Comment2>())
{
Transaction newTransaction = new Comment2();
newTransaction.Value = singleCitation;
newTransList.Add(newTransaction);
}
return newTransList;
}
Note: I have no idea if the above actually would compile, or if it actually copies all of the values needed. I reiterate: since you have not shown the TransList or Transaction data structures, it's not possible to know what all in them needs to be copied, nor what the best way to copy those values would be.
That said, note in the above example that this version of the method:
Creates an entirely new instance of the TransList object, storing the reference in newTransList.
For each Transaction value to be modified, it creates an entirely new instance of Transaction (using the appropriate type), assigning to that instance's Value property the modified value.
For each of those new Transaction objects, it adds the object to the newly-created TransList object referenced by the newTransList variable.
Finally, it returns that newly-created TransList object, rather than the one that was passed to the method.
Presumably you know what the correct way to add Transaction elements to TransList object, as well as whether there are other members in a Transaction object that would need to be copied. The above is simply a basic illustration of where and how you can modify your code so that you do the "deep copy" needed to avoid the problem you're describing.

Adding items to an IEnumerable through an extension method does not work?

In most of the methods I use that return some kind of collection I return IEnumerable rather than the specific type (e.g. List). In many cases I have another collection that I want to combine with the result IEnumerable, this would be exactly like taking a List and adding another List to it using the AddRange method. I have the following example, in it I have created an extension method that should take a collection of items to add and adds them to a base collection, while debugging this appears to works but in the original collection the items are never added. I don't understand this, why aren't they added, is there something about the implementation of the IEnumerable that I am missing? I understand that IEnumerable is a read only interface, but Iam not adding to this list in the example below, I am replacing it, but the original IEnumerable does not change.
class Program
{
static void Main(string[] args)
{
var collectionOne = new CollectionContainerOne();
var collectionTwo = new CollectionContainerTwo();
// Starts at 1- 50 //
collectionOne.Orders.AddRange(collectionTwo.Orders);
// Should now be 100 items but remains original 50 //
}
}
public class CollectionContainerOne
{
public IEnumerable<Order> Orders { get; set; }
public CollectionContainerOne()
{
var testIds = Enumerable.Range(1, 50);
var orders = new List<Order>();
foreach (int i in testIds)
{
orders.Add(new Order() { Id = i, Name = "Order #" + i.ToString() });
}
this.Orders = orders;
}
}
public class CollectionContainerTwo
{
public IEnumerable<Order> Orders { get; set; }
public CollectionContainerTwo()
{
var testIds = Enumerable.Range(51, 50);
var orders = new List<Order>();
foreach (int i in testIds)
{
orders.Add(new Order() { Id = i, Name = "Order #" + i.ToString() });
}
this.Orders = orders;
}
}
public class Order
{
public int Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return this.Name;
}
}
public static class IEnumerable
{
public static void AddRange<T>(this IEnumerable<T> enumerationToAddTo, IEnumerable<T> itemsToAdd)
{
var addingToList = enumerationToAddTo.ToList();
addingToList.AddRange(itemsToAdd);
// Neither of the following works //
enumerationToAddTo.Concat(addingToList);
// OR
enumerationToAddTo = addingToList;
// OR
enumerationToAddTo = new List<T>(addingToList);
}
}
You are modifying the parameter enumerationToAddTo, which is a reference. However, the reference is not itself passed by reference, so when you modify the reference, the change is not observable in the caller. Furthermore, it is not possible to use ref parameters in extension methods.
You are better off using Enumerable.Concat<T>. Alternatively, you can use ICollection, which has an Add(T) method. Unfortunately, List<T>.AddRange isn't defined in any interface.
Here is an example to illustrate the passing of reference types by reference. As Nikola points out, this is not really useful code. Don't try this at home!
void Caller()
{
// think of ss as a piece of paper that tells you where to find the list.
List<string> ss = new List<string> { "a", "b" };
//passing by value: we take another piece of paper and copy the information on ss to that piece of paper; we pass that to the method
DoNotReassign(ss);
//as this point, ss refers to the same list, that now contains { "a", "b", "c" }
//passing by reference: we pass the actual original piece of paper to the method.
Reassign(ref ss);
// now, ss refers to a different list, whose contents are { "x", "y", "z" }
}
void DoNotReassign(List<string> strings)
{
strings.Add("c");
strings = new List<string> { "x", "y", "z" }; // the caller will not see the change of reference
//in the piece of paper analogy, we have erased the piece of paper and written the location
//of the new list on it. Because this piece of paper is a copy of SS, the caller doesn't see the change.
}
void Reassign(ref List<string> strings)
{
strings.Add("d");
//at this point, strings contains { "a", "b", "c", "d" }, but we're about to throw that away:
strings = new List<string> { "x", "y", "z" };
//because strings is a reference to the caller's variable ss, the caller sees the reassignment to a new collection
//in the piece of paper analogy, when we erase the paper and put the new object's
//location on it, the caller sees that, because we are operating on the same
//piece of paper ("ss") as the caller
}
EDIT
Consider this program fragment:
string originalValue = "Hello, World!";
string workingCopy = originalValue;
workingCopy = workingCopy.Substring(0, workingCopy.Length - 1);
workingCopy = workingCopy + "?";
Console.WriteLine(originalValue.Equals("Hello, World!"); // writes "True"
Console.WriteLine(originalValue.Equals(workingCopy); // writes "False"
If your assumption about reference types were true, the output would be "False" then "True"
Calling your extensions method like this:
collectionOne.Orders.AddRange(collectionTwo.Orders);
Is essentially the same as:
IEnumerable.AddRange(collectionOne.Orders, collectionTwo.Orders);
Now what happens there, is you pass copy of reference to the collectionOne.Orders to the AddRange method. In your AddRange implementation you try to assign new value to the copy. It gets "lost" inside. You are not assigning new value to collectionOne.Orders, you assign it to its local copy - which scope is only within the method body itself. As a result of all modifications happenining inside AddRange, outside world notices no changes.
You either need to return new enumerable, or work on lists directly. Having mutating methods on IEnumerable<T> is rather counterintuitive, I'd stay away from doing that.
What you want exists and is called Concat. Essentially, when you do this in your Main:
var combined = collectionOne.Orders.Concat(collectionTwo.Orders);
Here, combined will refer to an IEnumerable that will traverse both source collections when enumerated.
IEnumerable does not support adding. What you in essence did in your code is create new collection from your enumerable, and add items to that new collection. Your old collection still has same items.
E.g., you create a collection of numbers like this
Collection1 = [ 1, 2, 3, 4, 5 ]
when you do Collection1.ToList().Add(...) you will get new collection with same members, and add new members like so:
Collection1 = [ 1, 2, 3, 4, 5, 6, 7, ... ]
your old collection will however still hold old members, as ToList creates new collection.
Solution #1:
Instead of using IEnumerable use IList which supports modification.
Solution #2 (bad):
Cast your IEnumerable back to it's derived type and add members to it. This is quite bad though, in fact it's better to just return List in the first place
IEnumerable<Order> collectionOne = ...;
List<Order> collectionOneList = (List<Order>)collectionOne;
collectionOneList.Add(new Order());
General guideline (best):
If you are returning collections which are standard in .NET there is no reason to return their interfaces. In this case it's best to use original type. If you are however returning collection which you implemented yourself, then you should return an interface
It's a completely different case when you are thinking about input parameters. If your method is asking to enumerate over items, then you should ask for IEnumerable. This way you can do what you need over it, and you are placing least constraint on person who is calling it. They can send any enumerable. If you need to add to that collection, you may require IList so that you can also modify it in your method.
Basically the problem is that you can't assign a value to enumerationToAddTo partially because it isn't a reference parameter. Also as phoog mentions ToList() creates a new list and does not cast the existing IEnumerable to a list.
This isn't really a good use of a extension. I would recommend that you add a method to your container collection that allows you add add new items to the IEnumerable instance. This would better encapsulate the logic that's particular to that class.

Categories

Resources