I'm currently using Visual Basic for a College Project which requires us to make a simple database system. For my system I have a base(abstract) class called Record which is inherited by the different types of records there are in my database e.g. Member, User, Role.
I am saving my data in csv files and have already written a CSVHandler class. However, I want an elegant way of constructing an instance of a class derived from Record with a string from the CSVHandler.
This is where the problem occurs. The only way I can think of doing this is by making a Constrcutor or Shared Function in each class derived from Record. However, Visual Basic does not allow you make Constructors or Shared Functions also MustOverride.
Here is the code I would expect to write:
' Base Class
Public MustInherit Class Record
Public MustOverride Shared Function fromString(ByVal str as String) As Record
End Class
' Example Of Class Derived From Record
Public Class User
Inherits Record
Private _id As String
Private _name As String
Public Sub New(ByVal id As String, ByVal name As String)
_id = id
_name = name
End Sub
Public Overrides Shared Function fromString(ByVal str as String) As Record
Dim strs() As String = str.Split(",")
Return New User(strs(0), strs(1))
End Function
End Class
' Example Of Creating Instacnce Of User
Dim user1 = User.fromString("1671,Kappeh")
Is there a way to achieve this effect?
Have your constructor call a Protected MustOverride method that does the initialisation.
Public MustInherit Class Record
'This is required because each derived constructor must be able to implicitly invoke a parameterless
'base constructor if it doesn't explicitly invoke a base constructor with parameters.
Protected Sub New()
End Sub
Public Sub New(csv As String)
Init(csv)
End Sub
Protected MustOverride Sub Init(csv As String)
End Class
Public Class User
Inherits Record
Private Property Id As String
Private Property Name As String
'This is still required because you can use a base constructor directly to create a derived instance.
Public Sub New(csv As String)
MyBase.New(csv)
End Sub
Public Sub New(id As String, name As String)
Id = id
Name = name
End Sub
Protected Overrides Sub Init(csv As String)
'Add your type-specific implementation here.
End Sub
End Class
This "solution" doesn't actually do what I thought it would because, while it forces you to override Init in a derived class, you still have to provide a derived constructor that invokes the base constructor that calls Init and you still can't enforce that. I think that I'll leave this as an answer though, because, while it doesn't actually provide a solution to your problem, it demonstrates further why (as far as I can tell) there is no such solution.
The following is similar to the answer from #jmcilhinney in that it forces the derived class to implement an initialization method. However it makes use of a generic shared function and uses the little known GetUninitializedObject method to get around using the generic New constraint and it's requirement of an accessible parameter-less constructor.
Public MustInherit Class Record
Public Shared Function fromString(Of T As {Record})(ByVal str As String) As T
' create an unintialized instance of T
Dim ret As T = DirectCast(System.Runtime.Serialization.FormatterServices.GetUninitializedObject(GetType(T)), T)
ret.Initialize(str)
Return ret
End Function
Protected MustOverride Sub Initialize(source As String)
End Class
The User class then would be something like this:
Public Class User : Inherits Record
Private _id As String
Private _name As String
Public Sub New(ByVal id As String, ByVal name As String)
_id = id
_name = name
End Sub
Protected Overrides Sub Initialize(source As String)
Dim strs() As String = source.Split(","c)
_id = strs(0)
_name = strs(1)
End Sub
End Class
Example usage:
Dim userRecord As User = Record.fromString(Of User)("1,2")
Related
I'm writing a really basic function that sorts inbound emails to a group mailbox into folders based on who they're intended to be read by. It's a daft thing to need to do, as we all have our own email addresses, but companies often do daft things!
The criteria that determine "who the email is for" are quite complex, so I'm writing a series of simple functions to return if it's "for" a particular person
Function IsRichard(msg As Outlook.MailItem) As Boolean
...
End Function
Function IsTim(msg As Outlook.MailItem) As Boolean
...
End Function
At present I'm then running all of these in sequence in a control function Function WhoIsItFor(). However, this sub is now getting a bit long and ungainly, so I was wondering if it was possible to define an Array or Collection of Functions so that I can do something along the lines of:
Const AllFunctions as Function () = {IsRichard, IsTim ...}
Function WhoIsItFor(msg as Outlook.MailItem) as String
For Each thisFunction as Function in AllFunctions
if thisFunction(msg) then
return (thisFunction.name)
end if
next
end function
You should use the AddressOf operator and the define the array type with the delegate Func:
Dim AllFunctions() As Func(Of Outlook.MailItem, Boolean) = {AddressOf IsRichard, AddressOf IsTim}
Function WhoIsItFor(msg As Outlook.MailItem) As String
For Each thisFunction As Func(Of Outlook.MailItem, Boolean) In AllFunctions
If thisFunction(msg) Then
Return thisFunction.Method.Name
End If
Next
Return Nothing
End Function
Alternative approach is to create a class (vb.net is an object-oriented language ;)) to represent a person(or anything which can "react" to the email).
With such approach when you adding or removing persons you don't need to touch "main" code which handling emails.
' Create an abstraction to represent mail handler
Public Interface IMailHandler
Function CanHandle(MailItem mail) As Boolean
Sub Handle(MailItem mail)
End Interface
' Implement for Tim
Public Class Tim Inherits IMailPerson
Public Function CanHandle(MailItem mail) As Boolean
Return mail.Subject = "For Tim"
End Function
Public Sub Handle(MailItem mail)
' Move to Tim folder
End Sub
End Class
' Implement for Bob
Public Class Bob Inherits IMailPerson
Public Function CanHandle(MailItem mail) As Boolean
' Can be anything else
End Function
Public Sub Handle(MailItem mail)
' Delete Bob messages
End Sub
End Class
Usage
Dim handlers As New List(Of IMailHandler) From { New Tim(), New Bob() }
var handler = handlers.
Where(Function (h) h.CanHandle(mail)).
DefaultIfEmpty(new DefaultHandler()).
First()
handler.Handle(mail)
With approach above you can keep related logic in one place:
decision about "can this mail be handled"
actual action for this mail
I'm trying to access Label from another class's method running in background thread with the help of MainWindow Class's Public Shared Sub like this:
Private Delegate Sub ProgressReportInvoker(ByVal progressStr As String)
Public Shared Sub ProgressReport(ByVal progressStr As String)
If MainWindow.Label.Dispatcher.CheckAccess() Then
MainWindow.Label.Content = progressStr
Else
MainWindow.Label.Dispatcher.Invoke(
New ProgressReportInvoker(AddressOf ProgressReport),
progressStr)
End If
End Sub
Call from another class is below:
MainWindow.ProgressReport("Sample text")
But I have this error on "MainWindow.Label":
Reference to a non-shared member requires an object reference.
I noticed that if I declare Label in MainWindow.g.i.vb as Public Shared than error is gone:
#ExternalSource ("..\..\MainWindow.xaml", 11)
<System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")>
Public Shared WithEvents Label As System.Windows.Controls.Label
#End ExternalSource
But this file is generated automatically from the *.XAML file so it takes previous look when I compile the code.
Is there any way to make control shared in *.XAML file or may be there are any alternatives of making my task possible?
You should access the instance of the MainWindow and not the type itself:
Public Shared Sub ProgressReport(ByVal progressStr As String)
Dim mainWindow = Application.Current.Windows.OfType(Of MainWindow).FirstOrDefault()
If mainWindow.Label.Dispatcher.CheckAccess() Then
mainWindow.Label.Content = progressStr
Else
mainWindow.Label.Dispatcher.Invoke(
New ProgressReportInvoker(AddressOf ProgressReport),
progressStr)
End If
End Sub
I tried this before but problem is in multitasking. I can't access the form from another thread without some special moves which I don't know about
You can only access a UI control in the thread on which it was originally created:
Application.Current.Dispatcher.BeginInvoke(New Action(Sub()
Dim mainWindow = Application.Current.Windows.OfType(Of MainWindow).FirstOrDefault()
mainWindow.Label.Content = progressStr
End Sub))
It is very bad practice to use anything global (shared/static). Use instance of class or other mechanism (Dependency Injection, messaging, events, etc.) for communication between independent classes.
I dont really understand what I do wrong when I try to Deserialize the JSON stuff I get.
You can get the response from http://api.steampowered.com/ISteamApps/GetAppList/v2?format=json
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim SteamGameInfoObject As Object = JsonConvert.DeserializeObject(Of Object)(New System.Net.WebClient().DownloadString("http://api.steampowered.com/ISteamApps/GetAppList/v2?format=json"))
End Sub
Public Class SteamRootObject
Public Class SteamAPIJSONResult
Public Property applist As List(Of applistlist)
End Class
Public Class applistlist
Public Property apps As List(Of appslist)
End Class
Public Class appslist
Public Property appid As Integer()
Public Property name As String()
End Class
End Class
End Class
I dont know how to "To fix this error either change the JSON to a JSON array"
You have a few problems here:
You define appid and name as arrays of integers and strings, respectively. In the JSON these are primitive values not arrays, so the VB.NET properties themselves should be declared to be simple integers and strings.
You define applist as a List(Of applistlist). Despite the name, the property value in the JSON is a single object not an array, so the property should refer to a single object also.
In Form1_Load you need to deserialize to the correct type, SteamRootObject. If you do JsonConvert.DeserializeObject(Of Object)(jsonString) you are not telling Json.NET the type to which to deserialize!
Stylistically there is no need to nest the inner classes inside the root class, it adds to complexity.
There are plenty of on-line tools to auto-generate .Net classes from JSON that can be found quickly using google, for instance http://json2csharp.com/ (for c#) or http://jsonutils.com/ or http://www.httputility.net/json-to-csharp-vb-typescript-class.aspx. In the future, use one of these to check your work.
Thus your fixed classes should look like:
Public Class App
Public Property appid As Integer
Public Property name As String
End Class
Public Class Applist
Public Property apps As New List(of App)
End Class
Public Class SteamRootObject
Public Property applist As Applist
End Class
And, to deserialize:
Dim SteamGameInfoObject = JsonConvert.DeserializeObject(of SteamRootObject)(jsonString)
Prototype fiddle.
I have something similar to the flowing two simple classes:
<DataContract> _
Public Class C1
Public Sub New(x_ As Int64)
_x= x_
End Sub
<DataMember()> _
Public Property x As Int64
End Class
<DataContract> _
Public Class C2
Public Sub New(y_ As string)
_y= y_
End Sub
<DataMember()> _
Public Property y As string
End Class
and the following structure that made of both:
<DataContract, StructLayout(LayoutKind.Sequential), _
KnownType(GetType(C1)), KnownType(GetType(C2))> _
Public structure S1
<DataMember, MessageHeaderArray> _
Public Second() As C1
<DataMember> _
Public First As C2
End structure
Finally, I'm calling a sub from a server's interface that sends an array of those structures as follows:
<ServiceContract(CallbackContract:=GetType(IClient))> _
Public Interface IServer
<OperationContract> _
Sub SendToServer(ByVal S()As S1)
End Interface
The problem is when I'm trying to pass this using WCF I'm getting a strange exception of the following format:
The use of type 'S1' as a get-only collection is not supported with NetDataContractSerializer. Consider marking the type with the CollectionDataContractAttribute attribute or the SerializableAttribute attribute or adding a setter to the property.
I tried to use the two suggested attributes, but I found that they should be used on declaring new class collection of types public class xx(Of T), not for my case (an exception telling that when used), I really can't understand about which get-only property he speaking about, because I have none, anyway, removing Public Second() As C1 from the structure, or passing one instance of the structure instance of an array, fixes the problem.
So I tried to change S1 to a class instead of structure, and the array into a List with get & set parts, but nothing helps, any clue what is going here?
Is there a reason you are using NetDataContractSerializer? Unless you are trying to support some legacy code, I believe you should use DataContractSerializer.
I am having difficulty loading some child attributes of a given entity. I have managed to load child entities on other objects, but not this one. To add to the frustration, the child entities I am trying to load are referenced from another Entity, and from this they work fine...
My code is as follows.
ViewWasteApplication Page
Protected Overrides Sub OnNavigatedTo(ByVal e As System.Windows.Navigation.NavigationEventArgs)
If NavigationContext.QueryString.ContainsKey("ID") Then
' load an existing record - edit mode
Context.Load(Context.GetWasteApplicationsByIDQuery(Int32.Parse(NavigationContext.QueryString("ID").ToString())),
AddressOf wasteApplicationLoaded, Nothing)
Else
MessageBox.Show("Application not found")
End If
End Sub
This calls GetWasteApplicationsByID - which is as follows:
Public Function GetWasteApplicationsByID(ByVal query As Int32) As IQueryable(Of WasteApplication)
Dim result = Me.ObjectContext.WasteApplications.Include("CurrentlyWith") _
.Include("Packaging") _
.Include("WasteStream") _
.Where(Function(f) f.ID = query)
Return result
End Function
The WasteApplication is being returned, but neither of the 3 child entities are appearing.
I have created a MetaData class for this WasteApplication, as follows:
<MetadataTypeAttribute(GetType(WasteApplications.WasteApplicationsMetaData))> _
Partial Public Class WasteApplications
Friend NotInheritable Class WasteApplicationsMetaData
'Metadata classes are not meant to be instantiated.
Private Sub New()
MyBase.New()
End Sub
Public Property ID As Integer
Public Property RequestedByName As String
Public Property RequestedByExtension As String
Public Property CARQRequired As Boolean
Public Property OriginOfMaterialID As Integer
Public Property Comments As String
Public Property MaterialName As String
Public Property PackagingID As Integer
Public Property FacilityPath As String
Public Property ProcessStatus As String
Public Property DateSubmitted As DateTime
Public Property RequestedByUsername As String
Public Property CurrentlyWithID As Integer
Public Property WasteStreamID As Integer
<Include()>
Public Property WasteStream As WasteStreams
<Include()>
Public Property Packaging As Packaging
End Class
End Class
Can anyone see anything wrong with this? I load the same two child objects on another page, and these seem to load just fine. The code for this is as follows:
View Chemical Application (This works)
Protected Overrides Sub OnNavigatedTo(ByVal e As System.Windows.Navigation.NavigationEventArgs)
Context.Load(Context.GetChemicalApplicationsByIDQuery(Int32.Parse(NavigationContext.QueryString("ID"))),
AddressOf wasteApplicationLoaded, Nothing)
End Sub
With the RIA function as follows:
Public Function GetChemicalApplicationsByID(ByVal query As Int32) As IQueryable(Of ChemicalApplication)
Return Me.ObjectContext.ChemicalApplications.Include("Chemical") _
.Include("ProcessStatus") _
.Include("PlanningApprover") _
.Include("FacilitiesApprover") _
.Include("MaintenanceApprover") _
.Include("PPCPermit") _
.Include("DischargeConsent") _
.Include("Facility") _
.Include("Packaging") _
.Include("WasteStream") _
.Where(Function(f) f.ID = query)
End Function
Any suggestions?
NOTE: I have not posted any of the XAML bindings, as I have confirmed via debugging that the source entities do not contain the child data, so therefore binding will not be an issue.
I am using Silverlight 4 with Entity Framework 4.
You will need to create metadata classes for the entities you wish to include and mark the fields with the [Include] attribute.
[MetadataTypeAttribute(typeof(Client.ClientMetadata))]
public partial class Client
{
internal sealed class ClientMetadata
{
private ClientMetadata()
{
}
[Required(ErrorMessage="You must enter a client name")]
public string Name;
[Include]
public EntityCollection<Contact> Contacts;
[Include]
public Employee Employee;
[Include]
public BillTo BillTo;
}
}
See RIA Services and relational data for more.
You need to do the 2 following things in order for this to work:
a) Add the include parameter in a metadata class (as DaveB proposed)
b) on the domain service queries (server side) add the include directive - Me.ObjectContext.WasteApplications.Include("CurrentlyWith")
I managed to solve the issue, you need to load the respective entities into the datacontext as well as loading the linked entity. i.e in my case I need to add:
Context.Load(Context.GetWasteStreamsQuery())
Context.Load(Context.GetPackagingQuery())
so that the linked child entities exist. My full onNavigate therefore looks like this:
Protected Overrides Sub OnNavigatedTo(ByVal e As System.Windows.Navigation.NavigationEventArgs)
If NavigationContext.QueryString.ContainsKey("ID") Then
Context.Load(Context.GetWasteApplicationsByIDQuery(Int32.Parse(NavigationContext.QueryString("ID").ToString())),
AddressOf wasteApplicationLoaded, Nothing)
Context.Load(Context.GetWasteStreamsQuery())
Context.Load(Context.GetPackagingQuery())
Else
MessageBox.Show("Application not found")
End If
End Sub