I have a collection, "entity.DestinationDetailscollection". I then copied this collection to an another collection, "DestinationCollection" as shown in the below code. But whenever i clear the source collection(i.e entity.DestinationDetailscollection), the collection "DestinationCollection " is also getting cleared. Please help.
Dim DestinationCollection As New Collection(Of StudentDTO)
DestinationCollection = New Collection(Of StudentDTO)(entity.DestinationDetailscollection)
entity.DestinationDetailscollection.Clear()
If you look at the documentation of Collection(Of T), you'll find that Collection(Of T) constructor does NOT copy the source collection supplied as the parameter. It just acts as a wrapper. So clearing one will clear the other.
This behavior is different from List(Of T) constructor, which actually copies the source collection elements (just object references are copied) into the new List object and thus creates a separate list.
Though you could always copy the entire list manually to the new collection object (a simple loop), you should prefere List(Of T) over Collection(Of T).
As a side note, you're instantiating your Collection twice. You should dod it in one line:
Dim DestinationCollection As New Collection(Of StudentDTO)(entity.DestinationDetailscollection)
You should replace your first two lines with this one line.
Related
I’m new to OOP and VB.NET, so please bear with me.
In VB.Net I currently have working code that defines, for each form, significant information about selected controls on the form.
I have defined in a public class:
Public Class FormFld
Public Property ScrField As Control ' A control on the form
Public Property DbField As String ' Its corresponding field name in the database
… ' Other info about the control or its database field
End Class
When each form loads, I create a list of FormFlds for the form’s selected controls:
At the start of each form
ReadOnly FormFlds As New List(Of FormFld)()
and in the form’s Load routine
FormFlds.Add(New FormFld With {.ScrField = Control1Name, .DbField = "Field1Name", …})
FormFlds.Add(New FormFld With {.ScrField = Control2Name, .DbField = "Field2Name", …})
…
This technique has worked well to easily loop through the selected fields and, on input, populate those fields from the database, or, on output, write those field values to the database.
With this implementation, however, the list must be built every single time the form is loaded. I’m wondering if the setup of the list can be done only once, during program initialization, before the forms are loaded.
Here’s the latest that I have tried.
In Class1:
Public Class indiv
Public FormFlds As List(Of FormFld)() ' The list for the frmIndividual form
End Class
In Module1, I attempt defining the FormFlds for the eventual form frmIndividual, to be saved as Indiv.FormFlds. I’d like the form name (f) and the list owner (owner) to be defineable so I can easily change those for each form.
Dim owner As New indiv
Dim f As FrmIndividual
owner.FormFlds.Add(New FormFld With {.ScrField = f.TxtKey, .DbField = "Sort_Key", …})
In Visual Studio, the third line shows error “BC30456: 'Add' is not a member of 'List(Of FormFld)()”.
Wondering if the problem might be due to not having an actual form FrmIndividual created yet, I tried changing the second line to
Dim f As New FrmIndividual
but it didn’t change anything.
I’m using VS 2022, v17.2.1. If there’s more info you need, please let me know.
The reason that you're told there's no Add method is because arrays have no Add method and you have an array. Here:
ReadOnly FormFlds As New List(Of FormFld)()
You are using the New keyword to invoke a constructor, so the parentheses at the end are for the argument list for that method call. Here:
Public FormFlds As List(Of FormFld)()
there's no New keyword so there's no constructor, so the parentheses at the end indicate that the field is an array type. That code is functionally equivalent to this:
Public FormFlds() As List(Of FormFld)
When you get that field you're getting a reference to an array of List(Of FormFld), not just a single List(Of FormFld) object. Of course, the field is initially Nothing anyway, so you'd have to assign something to it first to be able to use it.
By the way, the error message was already telling you what the issue was:
'Add' is not a member of 'List(Of FormFld)()`
It is telling you that Add is not a member of array of List(Of FormFld), which it's obviously not.
I've had to update a vb.net project from .NetFramework 4 to .NetFramework 4.7.2. In the process the following code is now throwing an error
Dim actuatorModelsArr = DirectCast(retNumberList, Array) Dim
Dim actuatorModels = actuatorModelsArr.Cast(Of ACTUATORMODELS)().ToList()
The error is System.ArrayTypeMismatchException: Source array type cannot be assigned to destination array type.
retNumberList is an Integer array
ACTUATORMODELS is an Enum
in the .netFramework 4 version actuatorModels is a list of the Enum
{System.Collections.Generic.List`1[FisherIECLib.ACTUATORMODELS]}
The list is used later in the module via linq to grab one of the Enums as a return value.
Is there a way around this or a way to create a list of the Enum?
Thanks in advance,
Hank
I think it's interesting that it stopped working. You can replace the code with a Select and cast to make it work
Dim retNumberList = {1, 2, 3, 4}
' either of these will produce a List of your Enum
Dim a = retNumberList.Select(Function(i) DirectCast(i, ACTUATORMODELS)).ToList()
Dim b = retNumberList.Select(Function(i) CType(i, ACTUATORMODELS)).ToList()
As for the original not working:
Dim c = retNumberList.Cast(Of ACTUATORMODELS)().ToList()
The literature suggests this is the equivalent of a (type)obj c# style cast, but both versions of vb.net cast work in the select. I am not sure why.
djv's answer helps fix your problem. Hopefully this answer will explain what's going wrong. If you look at the reference source for Cast(Of T) on an untyped IEnumerable, you'll find that the first thing that happens is the C# equivalent of this:
Dim asTyped = TryCast(source, IEnumerable(Of TResult))
If asTyped IsNot Nothing Then Return asTyped
Surprisingly, this cast will work for Integer() to IEnumerable(Of ACTUATORMODELS). This sets the issue in motion, because when it comes to making a List(Of T) out of the resulting sequence, it turns out that Integer() and ACTUATORMODELS() are actually not interchangeable, but Cast has already treated the sequence as though they are.
Based on some testing, this issue seems to arise out of the interaction of this corner case with a corner case in how the List(Of T) range constructor works and the more general corner case of Integer() vs TEnum().
In the general case of iterating over Integer() as if it were IEnumerable(Of TEnum), it works. You can make a For Each loop over the sequence, the enumeration variable will have TEnum as the type, and you'll see the values as if they were TEnum.
The problem comes in the range constructor for List(Of T), and an optimization there for a source that implements ICollection(Of T). In that case, the ctor will try to use ICollection(Of T).CopyTo to copy the items into the list's internal storage. This is where the error ultimately occurs, because (going back to the implementation of Cast(Of T)) the source is still the Integer() array, and Array.Copy (via Array.CopyTo) is not OK with trying to do that with a destination of TEnum().
I feel like this is a bug somewhere, though I'm not sure if it's in Cast(Of T), the List(Of T) range ctor, or in the array copy handling. I'm also not sure it's something that will get fixed, since it's a corner case that hits what look like significant optimizations and would require a very specialized check to catch.
I want to copy the content of one List(Of Object) to another and modify a single value. Is there a way to remove the reference?
In the code sample I get the output 'test2 test2' when I expect 'test1 test2'.
Module Module1
Sub Main()
Dim ListOfSample As New List(Of sample)
Dim var1 As New sample
var1.Name = "test"
ListOfSample.Add(var1)
ListOfSample.Add(var1)
Dim NewListOfSample As New List(Of sample)
NewListOfSample.AddRange(ListOfSample)
NewListOfSample(1).Name = "test2"
Console.Write(NewListOfSample(0).Name & " " & NewListOfSample(1).Name)
End Sub
End Module
Public Class sample
Public Name As String
End Class
Since your list is a list of Objects, when you perform add range, you are not adding "copies", instead you are adding the pointers (references) to the same objects that are in your original list.
You will need to clone all of your objects in the first list, and then add those clones to your second list. When it comes to cloning there are several different ways in .NET. Here's a post on getting deep copies of objects that does a good job explaining your options: Deep Copy of an Object
You can either create a clone method on your "sample" object to return a newly initialized copy of itself, or you can use some of the serialization methods mentioned in the post I linked to.
In the line NewListOfSample.AddRange(ListOfSample) you're adding references to your new list. So whatever you change in your new list will update the reference in your original list (they're both pointing to the same objects). You need to add new instances of Sample to the second list for it to contain independent items.
I have a webpage with a form that is used to edit some object. This object contains a Collection of other objects defined like this:
Public Overridable Property Employees As List(Of Employee)
On a form I can delete an employee, add a new one or modify existing one. When I click save new values are sent to the server. On a server I check if the user exists. If exists then I modify its values, if it does not exist then I add it. All employees that exist on the server and were not sent are marked as deleted (State changed to EntityState.Deleted). I try to use the following code (dbCollection = database entities, newCollection = collection sent from the form):
For Each item In dbCollection
Dim dbItem = item
Dim newTask = newCollection.FirstOrDefault(Function(i) i.Id = dbItem.Id)
If newTask Is Nothing Then
Me.Entry(item).State = EntityState.Deleted
Else
Me.Entry(item).CurrentValues.SetValues(newTask)
newCollection.Remove(newTask)
End If
Next
For Each item In newCollection
dbCollection.Add(item)
Next
Me.SaveChanges()
This code does not work, because changing to EntityState.Deleted removes the object from collection, and for each loop breaks, since the collection is modified...
I know that I can overcome this problem by using a for loop or adding objects to delete to some other list first, but I hope maybe there is a pattern that would make my code nicer.
Thanks in advance for all suggestions.
Replace...
For Each item In dbCollection
...by:
For Each item In dbCollection.ToList()
ToList() will create a copy of the collection (only the references, not the objects themselves). dbCollection.ToList() is another collection than dbCollection so that you safely can modify the dbCollection without getting the "collection has been modified" exception in the For Each loop.
I have this code to return a list of fund sources for our organization.
Dim FundSourceList As New List(Of FundSource)
Dim fs As New FundSource
If results.Count > 0 Then
For Each result In results
fs.FundID = result.Item("strFundID")
fs.FundDescription = result.Item("txtFundIDDescr")
fs.ShortFundDescription = result.Item("txtFundIDDescrShort")
FundSourceList.Add(fs)
Next
End If
Return FundSourceList
The problem is that when I loop through the resulting FundSourceList all it shows is the last value. For example, if I have three fund sources (state, federal, athletic), then when I use this code to loop through all I get listed is athletic, athletic, athletic.
For Each FundSource In FundSources
Debug.Print(FundSource.FundDescription)
Next
So I change the code to this. I moved the creation of the fs variable inside the loop.
Dim results = From result In dsResult.Tables(0) Select result
Dim FundSourceList As New List(Of FundSource)
If results.Count > 0 Then
For Each result In results
Dim fs As New FundSource
fs.FundID = result.Item("strFundID")
fs.FundDescription = result.Item("txtFundIDDescr")
fs.ShortFundDescription = result.Item("txtFundIDDescrShort")
FundSourceList.Add(fs)
Next
End If
Return FundSourceList
This works fine but now I'm creating a new class over and over again. It seems a little inefficient to me. Can I not create the class outside the loop and use it over and over again? Thanks.
If you have 3 fund sources, you need three FundSource objects. It's as simple as that. I don't know what's inefficient about it...
How can you add 3 fund sources to your list but just create one?
You're not actually creating a class - the class is the code definition for the methods and properties. When you use the New operation, you're creating an instance of that class, which results in an object. When you have a list of objects, like FundSourceList, you want the items in it to be individual objects. So yes, the solution you have at the bottom is correct. You mention efficiency concerns - when you instantiate the object, basically all that is happening (in this case) is some memory is being allocated to store the variables (and some references for the managed memory, but you don't need to worry about that here). This is necessary and is optimized under-the-hood, so you shouldn't need to worry about that either.
You can't instantiate the object outside of the loop to achieve the result you're after.
This is because your object would be a reference type.
By instantiating outside of the loop, you would create one reference to your object.
When iterating through your results and setting the properties, you'll be using that same reference over and over.
All you're adding to the list on each iteration is the same reference, which by the end of the loop, will refer to an object containing the last values in your result set.
By creating new objects inside the loop, you create new references - each pointing to a new FundSource. Your loop now writes into a fresh object, and get your desired results.