c#的多态问题

本文关键字:问题 多态 | 更新日期: 2023-09-27 18:03:54

我有一个DB表的用户,一些是'代理'和一些是'客户端'。在我的c#项目中,我有一个User超类和一个代理和客户端子类。Agent和Client扩展User.

我有一些基本的问题时,铸造或更改用户对象的代理或客户端对象。我也不知道为什么。这可能是相当基本的,但我不知道哪里出错了。

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()
    {
    }
}

为什么我不能这样做呢:

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;
    }

c#的多态问题

因为你用

一行创建它
User user = new User();

它不能神奇地变成它的一个子类(Agent)。您需要它来创建类型,因为它应该。

你应该做的是在开始…

if (type == "agent")
{
           user = new Agent();

基本上我认为你误解了多态性。您可以将实例向上转换为它的父实例之一,即

User user = new Agent();
....Later....
Agent agent = user as Agent;
....or.....
Agent agentTwo = new Agent;
User agentAsUser = agentTwo as User;

但是你不能用另一种方式投射。如果你仔细想想,这是有道理的——当应用程序创建内存来保存数据时,它只知道你用new告诉它什么。

如果您将对象实例化为基类,则不能从基类强制转换为子类。

您正在尝试使用as根据您的数据从User转换为ClientAgent。但是,您在函数的开头明确地创建了一个User对象:

User user = new User();

该对象是User类型,因此as将无法将其转换为ClientAgent,并将返回null。参考这里的文档。

as操作符类似于强制类型转换,只是在转换失败时返回null,而不是引发异常。

您可以这样演示:

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

你需要做的是显式地创建你需要的最具体的类,一个新的AgentClient,然后在需要的时候将其转换为一个更通用的User

例如:

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;
        }
    }

你不能将一个实例化为父类的对象强制转换为子类,因为它不是那种类型的,也就是说,User类型的对象永远不能是Agent类型的。

您需要重构代码,以便根据从数据库检索到的类型将对象实例化为正确的具体类。

您已经将用户声明为User,而不是AgentClient。因此,您不能将该对象强制转换为AgentClient,因为它不是AgentClient,而是User

你必须修改你的代码,使它看起来像这样。(片段):

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.
    }
}

user变量的实例是User类型,在这种情况下,您不能将base类强制转换为派生类

我建议将User改为abstract class,提供新的方法

abstract User BuildFromDataReader(IDataReader) 

所以ClientAgent将提供自己的实现从DataReader

多态性意味着您可以将Agent的实例视为User,而不是将User的实例视为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;
}

您可以将returnUser定义为User,但必须使用正确的类型创建它,例如:

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;
}

补充其他答案:想象它会像您写的那样工作。考虑这个场景:

var ape = new Ape();
var animal = ape as Animal;      // Animal is base class of Ape and Giraffe
var giraffe = animal as Giraffe;

如果最后一行确实会产生一个非空的长颈鹿对象,那么你就神奇地把猿变成了长颈鹿。

所以基本上:你总是可以将子类型强制转换为父类型,但是你只能将父类型强制转换为子类型,如果所讨论的对象实际上是该子类型或其子类型

每辆自行车都是一辆车,但是每辆车都是自行车吗?

一种可能的方法,你可以实现你想要做的是在代理和客户端类上有一个构造函数,它接受一个User参数(本质上使它们成为User类的装饰器)

因此,

public class Agent : User
{
   public Agent(User user)
   {
   }
}

在GetUser(int userid)方法中,你可以这样做

if (type == "agent")                        
{                            
    Agent agent = new Agent(user);
    agent.Company = rdr["company"].ToString();                           
    ..
    ..
    returnuser = agent;                        
 }

希望对事业有所帮助。