无法将源类型转换为目标类型。

6
我有一个子类实现了我的接口,符合合同的要求,但是当我尝试在子类构造函数中设置当前会话时,编译器报错,提示无法将变量类型与GetCurrentSession()返回类型进行比较:

"Cannot convert source type IAPISession to target type FacebookSession"

为什么会这样?Facebook不就是IAPISession吗?我想到的是多态,所以这种比较应该没问题。但我不确定。

public class FacebookSession : IAPISession
{
    private FacebookSession currentSession;

    private FacebookSession()
    {
        currentSession = GetCurrentSession();
    }

    ...more code

    public IAPISession GetCurrentSession()
    {
        // my logic is here...whatever that may be
    }
     ... more code
 }

Updated

here's my actual interface:

public interface IAPISession
{
    #region Properties

    int SessionID { get; }

    string UserID { get; }

    bool SessionHasExpired { get; }

    DateTime ExpirationDate { get; }

    void LogOut(); // expires the session & sets SessionHasExpired

    #endregion Properties

    #region Methods

    IAPISession GetCurrentSession();

    #endregion Methods

}

这个接口将被用于我们的任何API包装器项目(例如FlickrSession等)。


只是一个快速的评论。我看到很多人在这里追求单例,因为它看起来和功能上都像单例。但在我的情况下,会话的使用是一对一的关系..用户到唯一会话。一个用户将拥有许多独特类型的会话(例如FacebookSession、PicasaSession、FlickrSession等)。因此,根据他们通过我们的代码登录到哪个API,UserA将拥有几个独特的会话。当从SwDevMan81的页面查看此页面时,它读取“让客户端访问其独特实例”。单例应用于多对一资源情况 - PositiveGuy
几天前,我以为只需要制作这些单例模式,但我的一位优秀的程序员朋友说,在我的情况下,这是错误的用法...你不应该在这里使用 Singleton,因为你没有分享任何东西...也就是说,一个会话是特定于一个人、一个线程的。并不是有许多线程的“用户”在使用“一个”特定会话。他们每个人都有多个自己的会话,基于他们登录的 API。当我看了《四人帮》的文章时,完全支持他所说的,在多对一(单例)关系中,它是有意义的。 - PositiveGuy
感谢大家的帮助,非常感激。 - PositiveGuy
5个回答

5
是的,这需要显式转换。您正在接收一个通用(接口)会话,但您想使用特定的(Facebook)方法/字段。
您可能会设想一种情况,即 GetCurrentSession() 返回不同类型的 IAPISession!
- 使用 currentSession = (FacebookSession)GetCurrentSession(); - 在转换周围使用 try 块以捕获此可能性。我假设如果它不是 FacebookSession 类型,您的代码会变得混乱,因此您需要处理该情况。
补充说明:
FacebookSession fbSess;
IAPISession     genSess;
FacebookSession getFbSession() { ... return this; }
IAPISession     getSession()   { ... return this; }

genSess = getSession();        // legal
genSess = getFbSession();      // legal - implicit cast works as FacebookSession 
                               // is always a kind of IAPISession
fbSess  = getFbSession();      // legal
fbSess  = getSession();        // ILLEGAL - not all IAPISession's will be
                               // kinds of FacebookSession
fbSess  = (FacebookSession) getSession();
                               // legal, but might throw a class cast exception
                               // if it isn't a FacebookSession.

同样地,
genSess = fbSess;              // ok, implicit cast to generic type
fbSess  = genSess;             // ILLEGAL, it may not be a FacebookSession
fbSess  = (FacebookSession) genSess; 
                               // legal but can throw an exception

所以,与抽象类不同的是...如果我这样做:MyMethod(SomeAbstractType type),我可以将任何继承SomeAbstractType的子类型传递给该方法,但是对于接口来说,同样的技术并非如此。仅仅因为我有了 MyMethod(IAPISession session) 并不意味着我可以传递已实现 IAPISession 接口的任何子类?所以对于接口来说,多态的工作方式不同吗?或者我只是完全错过了初学者需要考虑到的关于多态和接口概念的一些东西吗? - PositiveGuy
好的,我已经通过添加一个例子来回答这个问题。 - Sanjay Manohar
明白了,之前说错了。在多态领域中,接口与抽象类没有区别...你绝对可以像我在上次回复中提到的那样,在方法参数中用其子类型替换接口。谢谢。 - PositiveGuy

3

虽然GetCurrentSession方法可能实际上返回的是一个FacebookSession,但其返回类型是IAPISession,并且没有从接口到实现该接口的任何类的隐式转换。

要么将GetCurrentSession方法的返回类型更改为FacebookSession,要么将currentSession字段类型更改为IAPISession(如果有意义的话可以同时更改两者)。


但是我认为由于多态性(一个接口变量或者说方法参数可以接受任何实现了该接口的类传递到你的方法或设置给你的变量...就像抽象类方法参数或变量类型可以接受任何实现它的子类一样)。这不是使用接口的主要目的之一吗,因为它具有多态性吗? - PositiveGuy
我不能这么做,因为这样就无法满足我的接口契约。我的接口定义了一个抽象公共的IAPISession GetCurrentSession()方法,因为我的意图是让其他API类型的会话(如Flickr等)实现这个接口,并在我们的其他api项目中重用。这意味着,如果我将该方法的返回类型设置为IAPISession,那么我应该能够返回该子类型,即使返回类型被设置为IAPISession。 - PositiveGuy
4
多态性允许您将所有的 FacebookSession "视为" IAPISession,但是反过来却不行,因为一个 IAPISession 实际上可能是例如 TwitterSession - Daniel Renshaw
Daniel,我尝试将 GetCurrentSession 接口方法签名设置为 static,出于你所说的非常明确的原因,但是你不能在接口中声明静态方法,所以... - PositiveGuy
请参考http://en.wikipedia.org/wiki/Singleton_pattern 了解单例模式的相关信息(其中有C#的示例)。如何处理取决于您要做什么。您会为每种会话类型拥有一个“当前会话”还是只为整个应用程序/用户拥有一个“当前会话”,或者只有一种会话类型(尽管由于多态性,您可能不知道确切的会话类型)? - Daniel Renshaw
显示剩余7条评论

1

你需要相反的方式:

public class FacebookSession : IAPISession
{
    private IAPISession currentSession;

    private FacebookSession()
    {
        currentSession = GetCurrentSession();
    }

    ...more code

    public FacebookSession GetCurrentSession()
    {
        // my logic is here...whatever that may be
    }
     ... more code
 }

如果您想在类内部使用特定的实现,那么我会在构造函数中实例化该类。然后在您的GetCurrentSession类中返回该实例。例如:
public class FacebookSession : IAPISession
{
    private FacebookSession currentSession;

    private FacebookSession()
    {
        currentSession = new FacebookSession();
    }

    ...more code

    public IAPISession GetCurrentSession()
    {
        return currentSession;
    }
     ... more code
 }

就像丹尼尔所提到的那样,这接近于单例模式。可以在这里看到其实现方式。请查看.NET优化代码下的部分。


但如果您的逻辑需要特定的FacebookSession方法,则不太好。 - Sanjay Manohar
1
如果在接口上定义了GetCurrentSession(),那么它也需要返回类型为IAPISession - Rowland Shaw
在这种情况下,它可以直接返回IAPISession。 - SwDevMan81
这就是我不明白的地方,为什么在这种情况下将currentSession backing field变成接口类型而不是子类类型本身会有好处。我的意思是,我不打算以任何其他方式使用该私有变量...无法从中获得任何多态性的好处。那么,仅仅为了满足合同,将该局部字段类型设置为IAPISession是否真的过度设计?我认为不是...就像Sanjay所说,这是一个特定的子类型,我知道这个类型。如果我看到了意图或者有一个好的理由来做这件事,而不仅仅是“满足错误”,请向我们解释一下。 - PositiveGuy
请查看我在此线程中回复其他人的原因,为什么我不想在这里使用单例模式... - PositiveGuy
我以前用过单例模式,但这不是我想要的场景,因为这是一个会话(session)... - PositiveGuy

0
在C#中,除非有一个隐式转换定义,否则你不能从一个更通用的类型(在你的情况下是IAPISession)转换为一个更具体的类型(FacebookSession)而不使用显式转换
想一想,在这种情况下会发生什么:
var flickrSession = new FlickrSession();
var iApiFlickrSession = flickrSession as IAPISession;

var faceBookSession = iApiFlickrSession;

你可以保证FacebookSession的实例也是IAPISession的实例,但不能保证IAPISession的实例也是FacebookSession的实例。

在第二种情况下,编译器不知道该怎么做。

你可以尝试使用as运算符(在你的情况下可能有效)。如果转换失败,值将为null:

currentSession = GetCurrentSession() as FacebookSession;

3
当然可以从更通用的类型转换为更具体的类型。(在你的例子中,你会得到一个运行时错误,但如果类型匹配,它将正常工作)。 - James Curran
你甚至需要 "as" 吗?我的意思是,在你的第二行中,它不会通过多态性知道 flickrSession 是 IAPISession 吗? - PositiveGuy
我宁愿通过编译时错误来设计代码...这是一个好习惯,必须有意义,这就是为什么强类型是“好”的原因。 - PositiveGuy

0

当你想要这样做时,必须明确表达:

private FacebookSession() 
{ 
    currentSession = (FacebookSession) GetCurrentSession(); 
} 

请注意,如果GetCurrentSession()返回的对象实际上不是FacebookSession,它将抛出一个异常。
更新:
您似乎混淆了运行时和编译时发生的事情:
    currentSession = GetCurrentSession(); 

这段代码无法编译通过,因为虽然 GetCurrentSession 返回的对象在运行时可能是一个 FacebookSession 对象,但编译器只知道它实现了 IAPISession 接口,而可能有许多非 FacebookSession 的对象也实现了 IAPISession 接口。

    currentSession = (FacebookSession) GetCurrentSession(); 

在这里,你告诉编译器:“相信我——当我们运行它时,它将是一个FacebookSession对象。如果我撒谎了,你可以通过异常来终止运行!”

    currentSession = (int) GetCurrentSession(); 

这段代码无法编译。你试图说“相信我”之类的话,但编译器却揭穿了你的谎言,说:“胡说八道!IAPISession永远不可能是一个int类型。”


GetCurrentSession返回一个IAPISession,这个对象应该可以通过多态性进行交换。我认为这是可行的,因为FacebookSession实现了IAPISession接口。 - PositiveGuy
@CoffeeAddict:是的,所以你尝试的东西在运行时不会崩溃。但你仍然必须进行强制转换! - James Curran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接