Is it possible to search for an object by one of its properties in a Generic List?
Public Class Customer
Private _id As Integer
Private _name As String
Public Property ID() As Integer
Get
Return _id
End Get
Set
_id = value
End Set
End Property
Public Property Name() As String
Get
Return _name
End Get
Set
_name = value
End Set
End Property
Public Sub New(id As Integer, name As String)
_id = id
_name = name
End Sub
End Class
Then loading and searching
Dim list as new list(Of Customer)
list.Add(New Customer(1,"A")
list.Add(New Customer(2,"B")
How can I return customer object with id =1? Does this have to do with the "Predicate" in Generics?
Note: I am doing this in VB.NET.
Yes, this has everything to do with predicates :)
You want the Find(Of T) method. You need to pass in a predicate (which is a type of delegate in this case). How you construct that delegate depends on which version of VB you're using. If you're using VB9, you could use a lambda expression. (If you're using VB9 you might want to use LINQ instead of Find(Of T) in the first place, mind you.) The lambda expression form would be something like:
list.Find(function(c) c.ID = 1)
I'm not sure if VB8 supports anonymous methods in the same way that C# 2 does though. If you need to call this from VB8, I'll see what I can come up with. (I'm more of a C# person really :)
Generally you need to use predicates:
list.Add(New Customer(1, "A"))
list.Add(New Customer(2, "B"))
Private Function HasID1(ByVal c As Customer) As Boolean
Return (c.ID = 1)
End Function
Dim customerWithID1 As Customer = list.Find(AddressOf HasID1)
Or with inline methods:
Dim customerWithID1 As Customer = list.Find(Function(p) p.ID = 1)
You could also overload the equals method and then do a contains. like this
Dim list as new list(Of Customer)
list.Add(New Customer(1,"A")
list.Add(New Customer(2,"B")
list.contains(new customer(1,"A"))
the equals method then would look like this
public overrides function Equals(Obj as object) as Boolean
return Me.Id.Equals(ctype(Obj,Customer).Id
end Function
Not tested but it should be close enough.
If you are using .NET 3.5 this can be done with LINQ to Objects:
How to: Query an ArrayList with LINQ
If not, in .NET 2.0 you can use the Find method of the list.
The idea is that you will need to provide an method that return true if a property of your object satisfies a certain condition.
Related
After creating a List(Of T), I want to create aBoolean function. First, we will ask data to add an object in the list. However, in case this new object has the same "DNI" (String attribute from the class Aspirante), then we cannot include this new Object in the list. Therefore, it should be True when we have an Object with the same attribute and False when we don´t, so we can add the new object.
Below is the code I did:
Public Class Oposicion
Private datos As New List(Of Aspirante)()
Public Function Alta(ByRef objAspirante As Aspirante) As Boolean
If datos.Contains(objAspirante.DNI) Then
Return True
Else
datos.Add(objAspirante)
Return False
End If
End Function
End Class
However it doesn´t work. I have no clue on how to do it. Sorry if I was not clear enough.
This doesn't answer your question directly but it involves a significant amount of code, so it won't work in a comment.
You probably shouldn't be using a List(Of T) in the first place. The HashSet(Of T) already includes functionality to prevent adding duplicate items, so that may be a better option. If you want to compare objects on a specific property value then you need to first create a comparer based on that:
Public Class Thing
Public Property Stuff As String
End Class
Public Class ThingComparer
Implements IEqualityComparer(Of Thing)
Public Overloads Function Equals(x As Thing, y As Thing) As Boolean Implements IEqualityComparer(Of Thing).Equals
Return x.Stuff.Equals(y.Stuff)
End Function
Public Overloads Function GetHashCode(obj As Thing) As Integer Implements IEqualityComparer(Of Thing).GetHashCode
Return obj.GetHashCode()
End Function
End Class
You then create a HashSet(Of T) that uses that comparer to determine equality:
Dim things As New HashSet(Of Thing)(New ThingComparer)
You can then just add items as you please by calling Add. That will either add the new item and return True or it will not not add the duplicate item and return False:
Dim variousStuff = {"One", "Two", "One"}
For Each stuff In variousStuff
Dim something As New Thing With {.Stuff = stuff}
If things.Add(something) Then
Console.WriteLine($"'{stuff}' was added successfully.")
Else
Console.WriteLine($"'{stuff}' is a duplicate and was not added.")
End If
Next
The potential drawback is that HasSet(Of T) does not implement IList(Of T), so you cannot access items by index. It does implement ICollection(OF T) though, so it does have a Count property and you can enumerate it with a For Each loop.
You can create a List(Of String) containing the just the DNI property of each object in datos. Then see if the DNI of objAspirante is contained in lst.
Public Class Oposicion
Private datos As New List(Of Aspirante)()
Public Function Alta(objAspirante As Aspirante) As Boolean
Dim lst As New List(Of String)
For Each a As Aspirante In datos
lst.Add(a.DNI)
Next
If lst.Contains(objAspirante.DNI) Then
Return True
Else
datos.Add(objAspirante)
Return False
End If
End Function
End Class
If you can change the type of datos, this might be easier.
Public Class Oposicion
Private datos As New Dictionary(Of String, Aspirante)()
Public Function Alta(objAspirante As Aspirante) As Boolean
If datos.ContainsKey(objAspirante.DNI) Then
Return True
Else
datos.Add(objAspirante.DNI, objAspirante)
Return False
End If
End Function
End Class
If you want to stick with your existing List(Of Aspirante), then simply use .Any() and pass it a Lambda to determine if one already exists. It'd look something like this:
Public Function Alta(ByVal objAspirante As Aspirante) As Boolean
If Not datos.Any(Function(x) x.DNI = objAspirante.DNI) Then
datos.Add(objAspirante)
Return True ' your description and code did not match for these!
End If
Return False ' your description and code did not match for these!
End Function
Note my comment on the Return lines...your code and description did not match here.
When I try to use the .Where() method on a list, this does method does not seem to be defined if the list is of a generic type:
In my program, I have a class called Warning, and in another class, a list of warnings, defined as:
Dim warningList As List(Of Warning)
When I try to manipulate this list as:
Dim item = warningList.Where(Function(x) x.GetName() = "Foo").FirstOrDefault()
This works completely fine, but when I try it like this:
Dim itemList
if(type = "Warning") Then 'Please note that this condition is true...
itemList = warningList
End If
Dim item = itemList.Where(Function(x) x.GetName() = "Foo").FirstOrDefault()
I get an exception, stating that method .Where() is not defined for class Warning
Can anybody tell me why this is?
Thank you!
Now that you've edited your question it's clear.
You declare itemList without a type, so it's Object implicitly(in VB.NET with option strict set to Off which i strongly recommend against).
Now that you have declared a variable of type Object you can asssign any type to it. But you would have to cast it back to its real type List(Of Warning) to be able to use list or LINQ methods(which extend IEnumerable(Of T).
But instead declare it with the correct type:
Dim itemList As List(Of Warning)
if(type = "Warning") Then
itemList = warningList
End If
Dim item = itemList.Where(Function(x) x.GetName() = "Foo").FirstOrDefault()
Including to comment to explain why Warning is not related to this problem:
That's not the real code. If warningList is really a List(Of Warning)
you should be able to use Enumerable.Where(if LINQ is
imported). The fact that you assign this instance to another variable
(on declaration) doesn't change anything because that variable's type
is also a List(Of Warning). So itemList.Where should work too. Warning
has nothing to do with it because the type which is extended by Where
is IEnumerable(Of T), T can be any type(even Object). Since List(Of T)
implements IEnumerable(Of T) you can use Enumerable.Where on any list
(or array).
If you actually have multiple types and Warning is just one of it, you should implement a common interface. Here's an example:
Public Enum NotificationType
Warning
Info
[Error]
End Enum
Public Interface INamedNotification
ReadOnly Property Type As NotificationType
Property Name As string
End Interface
Public Class Warning
Implements INamedNotification
Public Sub New( name As String )
Me.Name = name
End Sub
Public Property Name As String Implements INamedNotification.Name
Public ReadOnly Property Type As NotificationType Implements INamedNotification.Type
Get
Return NotificationType.Warning
End Get
End Property
End Class
Now you can declare a List(Of INamedNotification) and fill it with whatever implements this interface, like the Warning class:
Dim notificationList As List(Of INamedNotification)
if type = "Warning" Then
itemList = warningList
Else If type = "Info"
itemList = infoList
End If
Dim item = notificationList.Where(Function(x) x.Name = "Foo").FirstOrDefault()
Dim objects As New List(Of Object)
With New Object
.prop1 = "Property 1"
.prop2 = "Property 2"
objects.add(.instance) 'i mean instance of New Object
End With
is it possible.
I ask new question because last question has mislead information and I don't give right answer. so here code.
No it is not possible. The With statement basically creates an implicit variable. All you can do with that variable is access members and there is no member that returns a reference to the object itself.
If you want succinct code to create, populate and add an object to a list then do this:
myList.Add(New SomeType With {.SomeProperty = someValue,
.SomeOtherProperty = someOtherValue})
Interestingly, you can make it work the way you wanted if you create your own extension method. I was under the impression that you could not extend the Object class but either I was wrong or that has changed because I just tried in VB 2013 and it worked. You can write a method like this:
Imports System.Runtime.CompilerServices
Public Module ObjectExtensions
<Extension>
Public Function Self(Of T)(source As T) As T
Return source
End Function
End Module
and then do something like this:
With New SomeType
.SomeProperty = someValue
.SomeOtherProperty = someOtherValue
myList.Add(.Self())
End With
I'm not sure that that really provides any benefit though, given the availability of the object initialiser syntax that I demonstrated first.
Hmmm... I just realised that that's not actually extending the Object class. It was my original intention to try to do so but then I realised that a generic method was better because it would then return the same type as you call it on. I did just test it with a non-generic method extending type Object and it did still worked though.
You should to create your own class By example :
Public Class Car
Private _NumberCar As Integer
Public Property NumberCar() As Integer
Get
Return _NumberCar
End Get
Set(ByVal value As Integer)
_NumberCar = value
End Set
End Property
Private _ColorCar As Color
Public Property ColorCar() As Color
Get
Return _ColorCar
End Get
Set(ByVal value As Color)
_ColorCar = value
End Set
End Property
Private _OwnerName As String
Public Property OwnerName() As String
Get
Return _OwnerName
End Get
Set(ByVal value As String)
_OwnerName = value
End Set
End Property
End Class
and in the Class where you want to add the cars object do this :
Dim CarList As New List(Of Car)
Dim item As New Car
With item
.NumberCar = 1243
.ColorCar = Color.Red
.OwnerName = "Ibra"
End With
CarList.Add(item)
strong text
I have a class with several properties.
Public Class test
Public Property a As String
Public Property b As String
Public Property c As String
Public Property d As String
Public Property e As String
Public Property f As String
Public Property g As String
End Class
In my VB.net code, I am assigning a value to each property.
I want to send the whole test class as one parameter, and use all the values inside it.
So that if I add extra parameters later on, I want them to be used dynamically, instead of writing this everytime:
Textbox1.text= test.a & test.b & test.c .......
Any way to do this?
Im not really writing the values in a textbox, but this is just an simplified example.
I think what you want is a property. You'll need to add a property to your class like:
Public Property Combination() As String
Get
Return a & b & c & d & e ...
End Get
End Property
Then to get the value you'd use
Textbox1.text = test.combination
(for more details you can see http://www.dotnetperls.com/property-vbnet)
I recommend you override the built-in ToString function. Also, to further simplify this, add a CType operator.
Public Class test
Public Property a As String
Public Property b As String
Public Property c As String
Public Property d As String
Public Property e As String
Public Property f As String
Public Property g As String
Public Shared Widening Operator CType(obj As test) As String
Return If((obj Is Nothing), Nothing, obj.ToString())
End Operator
Public Overrides Function ToString() As String
Return String.Concat(Me.a, Me.b, Me.c, Me.d, Me.e, Me.f, Me.g)
End Function
End Class
The you could just do:
Textbox1.text = test
There is a way to dynamically get and set the value of properties on any object. Such functionality in .NET is collectively referred to as Reflection. For instance, to loop through all of the properties in an object, you could do something like this:
Public Function GetPropertyValues(o As Object) As String
Dim builder As New StringBuilder()
For Each i As PropertyInfo In o.GetType().GetProperties
Dim value As Object = Nothing
If i.CanRead Then
value = i.GetValue(o)
End If
If value IsNot Nothing Then
builder.Append(value.ToString())
End If
Next
Return builder.ToString()
End Function
In the above example, it calls i.GetValue to get the value of the property, but you can also call i.SetValue to set the value of the property. However, reflection is inefficient and, if used inappropriately, it can lead to brittle code. As such, as a general rule, you should avoid using reflection as long as there is any other better way to do the same thing. In other words, you should typically save reflection as a last resort.
Without more details, it's difficult to say for sure what other options would work well in your particular situation, but I strongly suspect that a better solution would be to use a List or Dictionary, for instance:
Dim myList As New List(Of String)()
myList.Add("first")
myList.Add("second")
myList.Add("third")
' ...
For Each i As String In myList
Textbox1.Text &= i
Next
Or:
Dim myDictionary As New Dictionary(Of String, String)()
myDictionary("a") = "first"
myDictionary("b") = "first"
myDictionary("c") = "first"
' ...
For Each i As KeyValuePair(Of String, String) In myDictionary
Textbox1.Text &= i.Value
Next
Rather than giving the very specific case (which I did earlier), let me give a general example. Let's say that I have a function, called callingFunction. It has one parameter, called parameter. Parameter is of an unknown type. Let us then say that I wish to copy this parameter, and return it as a new object. For example, in pseudo code, something along the lines of...
Function callingFunction(ByVal parameter As Object) As Object
Dim newObj As New Object
'newObj has the same value as parameter, but is a distinctly different object
'with a different reference
newObj = parameter
return newObj
End Function
EDIT: Additional Information
The first time I posted this question, I received only one response - I felt that perhaps I made the question too specific. I guess I will explain more, perhaps that will help. I have an ASP page with 10 tables on it. I am trying, using the VB code behind, to come up with a single solution to add new rows to any table. When the user clicks a button, a generic "add row" function should be called.
The difficulty lies in the fact that I have no guarantee of the contents of any table. A new row will have the same contents as the row above it, but given that there are 10 tables, 1 row could contain any number of objects - text boxes, check boxes, etc. So I want to create a generic object, make it of the same type as the row above it, then add it to a new cell, then to a new row, then to the table.
I've tested it thoroughly, and the only part my code is failing on lies in this dynamic generation of an object type. Hence why I asked about copying objects. Neither of the solutions posted so far work correctly, by the way. Thank you for your help so far, perhaps this additional information will make it easier to provide advice?
You can't do this in general. And it won't be a good idea, for example, if parameter is of a type which implements the singleton pattern. If parameter is of a type which supports copying, it should implement the ICloneable interface. So, your function could look like this:
Function MyFunc(ByVal parameter As Object) As Object
Dim cloneableObject As ICloneable = TryCast(parameter, ICloneable)
If Not cloneableObject Is Nothing Then
Return cloneableObject.Clone()
Else
Return Nothing
End If
End Function
You could implement something like this:
Dim p1 As Person = New Person("Tim")
Dim p2 As Object = CloneObject(p1)
Dim sameRef As Boolean = p2 Is p1 'false'
Private Function CloneObject(ByVal o As Object) As Object
Dim retObject As Object
Try
Dim objType As Type = o.GetType
Dim properties() As Reflection.PropertyInfo = objType.GetProperties
retObject = objType.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, Nothing, o, Nothing)
For Each propertyInfo As PropertyInfo In properties
If (propertyInfo.CanWrite) Then
propertyInfo.SetValue(retObject, propertyInfo.GetValue(o, Nothing), Nothing)
End If
Next
Catch ex As Exception
retObject = o
End Try
Return retObject
End Function
Class Person
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Public Sub New()
End Sub
Public Sub New(ByVal name As String)
Me.Name = name
End Sub
End Class
Here's a simple class that will work for most objects (assumes at least .Net 2.0):
Public Class ObjectCloner
Public Shared Function Clone(Of T)(ByVal obj As T) As T
Using buffer As MemoryStream = New MemoryStream
Dim formatter As New BinaryFormatter
formatter.Serialize(buffer, obj)
buffer.Position = 0
Return DirectCast(formatter.Deserialize(buffer), T)
End Using
End Function
End Class