Reputation: 999
I have a DB table with users, some are 'agents' and some are 'clients'. In my C# project, I have a User superclass and an Agent and Client subclass. Agent and Client extends User.
I am having some basic problems when casting or changing a User object to an Agent or Client object. I don't really know why. It's probably rather basic, but I don't know what's wrong.
public class User
{
public int UserId { get; set; }
public string UserType { get; set; }
public DateTime DateCreated { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public User()
{
}
}
public class Agent : User
{
public string Company { get; set; }
public string CompanyReg { get; set; }
public string SecurityQuestion { get; set; }
public string SecurityAnswer { get; set; }
public string Description { get; set; }
public int AccountBalance { get; set; }
public bool WantsRequests { get; set; }
public string ImageUrl { get; set; }
public Agent()
{
}
}
public class Client : User
{
public string Country { get; set; }
public string IP { get; set; }
public Client()
{
}
}
Now why can't I do this:
public User GetUser(int userid)
{
User user = new User();
User returnuser = user;
string sql = "SELECT usertype, datecreated, email, name, phone, country, ip, company, companyreg, securityquestion, securityanswer, description, accountbalance, aothcredits, wantsrequests FROM ruser WHERE userid=@userid";
try
{
using (SqlConnection con = new SqlConnection(constr))
using (SqlCommand cmd = new SqlCommand(sql))
{
con.Open();
cmd.Connection = con;
cmd.Parameters.Add("@userid", System.Data.SqlDbType.Int).Value = userid;
using (SqlDataReader rdr = cmd.ExecuteReader())
{
if (rdr.Read())
{
user.UserId = userid;
user.UserType = rdr["usertype"].ToString();
user.DateCreated = (DateTime)rdr["datecreated"];
user.Email = rdr["email"].ToString();
user.Name = rdr["name"].ToString();
user.Phone = rdr["phone"].ToString();
string type = rdr.GetString(0);
if (type == "agent")
{
Agent agent = user as Agent;
agent.Company = rdr["company"].ToString();
agent.CompanyReg = rdr["companyreg"].ToString();
agent.SecurityQuestion = rdr["securityQuestion"].ToString();
agent.SecurityAnswer = rdr["securityanswer"].ToString();
agent.Description = rdr["description"].ToString();
agent.AccountBalance = (int)rdr["accountbalance"];
agent.WantsRequests = Convert.ToBoolean(rdr["wantsrequests"]);
returnuser = agent;
}
else //type == "client"
{
Client client = user as Client;
client.Country = rdr["country"].ToString();
client.IP = rdr["ip"].ToString();
returnuser = client;
}
}
}
}
}
catch (SqlException e)
{
throw e;
}
return returnuser;
}
Upvotes: 3
Views: 481
Reputation: 7568
You can't cast from a base class to a child class if you instantiate the object as the base class.
You are attempting to use as
to cast from a User
to a Client
or an Agent
depending on your data. However, you are explicately creating a User
object at the start of your function:
User user = new User();
This object is of type User
so as
will not be able to convert it to a Client
or an Agent
and will return null. See the documentation here.
The as operator is like a cast except that it yields null on conversion failure instead of raising an exception.
You can demonstrate this as follows:
User u = new User();
System.Console.WriteLine("u is User: " + (u is User));
System.Console.WriteLine("u is Agent: " + (u is Agent));
System.Console.WriteLine("u is Client: " + (u is Client));
// Should produce:
// u is User: true
// u is Agent: false
// u is Client: false
Agent a = new Agent();
u = a;
System.Console.WriteLine("u is User: " + (u is User));
System.Console.WriteLine("u is Agent: " + (u is Agent));
System.Console.WriteLine("u is Client: " + (u is Agent));
// Should produce:
// u is User: true
// u is Agent: true
// u is Client: false
What you need to do is to explicitly create the most specific class you need, either a new Agent
or Client
, then cast this to a more generic User
when and as you need to.
For example:
public User GetUser(int userid)
{
User user;
string sql = "...";
try
{
using (SqlConnection con = new SqlConnection(constr))
using (SqlCommand cmd = new SqlCommand(sql))
{
//.. Snip sql stuff ... //
string type = rdr.GetString(0);
if (type == "agent")
{
Agent agent = new Agent();
agent.Company = rdr["company"].ToString();
agent.CompanyReg = rdr["companyreg"].ToString();
agent.SecurityQuestion = rdr["securityQuestion"].ToString();
agent.SecurityAnswer = rdr["securityanswer"].ToString();
agent.Description = rdr["description"].ToString();
agent.AccountBalance = (int)rdr["accountbalance"];
agent.WantsRequests = Convert.ToBoolean(rdr["wantsrequests"]);
user = agent;
}
else //type == "client"
{
Client client = new Client();
client.Country = rdr["country"].ToString();
client.IP = rdr["ip"].ToString();
user= client;
}
// Now do generic things
user.UserId = userid;
user.UserType = rdr["usertype"].ToString();
user.DateCreated = (DateTime)rdr["datecreated"];
user.Email = rdr["email"].ToString();
user.Name = rdr["name"].ToString();
user.Phone = rdr["phone"].ToString();
return user;
}
}
}
}
catch (SqlException e)
{
throw e;
}
}
Upvotes: 6
Reputation: 56934
You've declared your user as a User
, not as an Agent
or a Client
. Therefore, you cannot cast that object to an Agent
or Client
, since it is not an Agent
or a Client
, it's a User
.
You'll have to change your code so that it looks like this. (Snippit):
using (SqlDataReader rdr = cmd.ExecuteReader())
{
if(rdr.Read())
{
User user;
string type = rdr.GetString(0);
if (type == "agent")
{
user = new Agent();
// Fill out Agent specific properties
var agent = user as Agent;
agent.Company = ...
}
else if( type == "client" )
{
user = new Client();
// Fill out Client specific properties
var client = user as Client;
}
else
{
throw new InvalidProgramException ("Unknown user-type");
}
// Fill out common User properties.
}
}
Upvotes: 2
Reputation: 2758
One possible way, you can achieve what you are trying to do is by having a constructor on Agent and Client classes which takes in an User argument (essentially making them Decorators of the User class)
therefore,
public class Agent : User
{
public Agent(User user)
{
}
}
So in your GetUser(int userid) Method, you can now do something like
if (type == "agent")
{
Agent agent = new Agent(user);
agent.Company = rdr["company"].ToString();
..
..
returnuser = agent;
}
Hope that helps the cause.
Upvotes: 0
Reputation: 1247
Because you created it with the line
User user = new User();
It can't magically morph into one of it's Subclasses (Agent) later on. You need it to create the type as it should be.
What you should be doing is towards the start...
if (type == "agent")
{
user = new Agent();
Basically I think you've misunderstood polymorphism. You can Upcast an instance to one of its parents i.e.
User user = new Agent();
....Later....
Agent agent = user as Agent;
....or.....
Agent agentTwo = new Agent;
User agentAsUser = agentTwo as User;
But you can't cast the other way. It stands to reason if you think about it - when the application creates the memory to hold the data it only knows what you told it with new.
Upvotes: 7
Reputation: 19020
To augment the other answers: Imagine it would work as you wrote it. Consider this scenario:
var ape = new Ape();
var animal = ape as Animal; // Animal is base class of Ape and Giraffe
var giraffe = animal as Giraffe;
If the last line would indeed result in a non-null Giraffe object then you would have magically transformed an Ape into a Giraffe.
So basically: You can always cast a child into a parent, but you can only cast from parent to a child iff the object in question is actually of that child type or a descendent thereof.
Upvotes: 0
Reputation: 7468
You can define returnUser as User, but must create it using the correct type, i.e. something like this:
public User GetUser(int userid)
{
User returnuser;
string sql = "SELECT usertype, datecreated, email, name, phone, country, ip, company, companyreg, securityquestion, securityanswer, description, accountbalance, aothcredits, wantsrequests FROM ruser WHERE userid=@userid";
try
{
using (SqlConnection con = new SqlConnection(constr))
using (SqlCommand cmd = new SqlCommand(sql))
{
con.Open();
cmd.Connection = con;
cmd.Parameters.Add("@userid", System.Data.SqlDbType.Int).Value = userid;
using (SqlDataReader rdr = cmd.ExecuteReader())
{
if (rdr.Read())
{
string type = rdr.GetString(0);
if (type == "agent")
{
Agent agent = new Agent();
agent.Company = rdr["company"].ToString();
agent.CompanyReg = rdr["companyreg"].ToString();
agent.SecurityQuestion = rdr["securityQuestion"].ToString();
agent.SecurityAnswer = rdr["securityanswer"].ToString();
agent.Description = rdr["description"].ToString();
agent.AccountBalance = (int)rdr["accountbalance"];
agent.WantsRequests = Convert.ToBoolean(rdr["wantsrequests"]);
returnuser = agent;
}
else //type == "client"
{
Client client = new Client();
client.Country = rdr["country"].ToString();
client.IP = rdr["ip"].ToString();
returnuser = client;
}
returnuser .UserId = userid;
returnuser .UserType = rdr["usertype"].ToString();
returnuser .DateCreated = (DateTime)rdr["datecreated"];
returnuser .Email = rdr["email"].ToString();
returnuser .Name = rdr["name"].ToString();
returnuser .Phone = rdr["phone"].ToString();
}
}
}
}
catch (SqlException e)
{
throw e;
}
return returnuser;
}
Upvotes: 1
Reputation: 18474
Polymorphism means that you can treat an instance of Agent as a User, not an instance of User as an Agent.
User returnuser;
string sql = "SELECT usertype, datecreated, email, name, phone, country, ip, company, companyreg, securityquestion, securityanswer, description, accountbalance, aothcredits, wantsrequests FROM ruser WHERE userid=@userid";
try
{
using (SqlConnection con = new SqlConnection(constr))
using (SqlCommand cmd = new SqlCommand(sql))
{
con.Open();
cmd.Connection = con;
cmd.Parameters.Add("@userid", System.Data.SqlDbType.Int).Value = userid;
using (SqlDataReader rdr = cmd.ExecuteReader())
{
if (rdr.Read())
{
string type = rdr.GetString(0);
if (type == "agent")
{
Agent agent = user as Agent;
agent.Company = rdr["company"].ToString();
agent.CompanyReg = rdr["companyreg"].ToString();
agent.SecurityQuestion = rdr["securityQuestion"].ToString();
agent.SecurityAnswer = rdr["securityanswer"].ToString();
agent.Description = rdr["description"].ToString();
agent.AccountBalance = (int)rdr["accountbalance"];
agent.WantsRequests = Convert.ToBoolean(rdr["wantsrequests"]);
returnuser = agent;
}
else //type == "client"
{
Client client = user as Client;
client.Country = rdr["country"].ToString();
client.IP = rdr["ip"].ToString();
returnuser = client;
}
returnuser.UserId = userid;
returnuser.UserType = rdr["usertype"].ToString();
returnuser.DateCreated = (DateTime)rdr["datecreated"];
returnuser.Email = rdr["email"].ToString();
returnuser.Name = rdr["name"].ToString();
returnuser.Phone = rdr["phone"].ToString();
}
}
}
}
catch (SqlException e)
{
throw e;
}
return returnuser;
}
Upvotes: 1
Reputation: 62484
Instance of a user
variable is of a User
type, you can not cast a base
class to a Derived in such case
I would suggest to make User
as abstract class
, provide new method
abstract User BuildFromDataReader(IDataReader)
so both Client
and Agent
will provide own implementation of how-to build from DataReader
Upvotes: 1
Reputation: 6793
You can't cast an object you have instantiated as a superclass to a subclass as it isn't of that type, i.e. an object of type User can never been a type of Agent.
You'll need to restructure your code so you instantiate the object as the correct concrete class depending on the type you retrieve from the database.
Upvotes: 5