C#3.0中的“多重继承”

    C#的对象系统是个单根系统,不支持类的多继承,只支持多接口实现,这在某种程度带来了一些不便:我们在系统设计时经常会抽象出一些接口,并为接口提供一个抽象类作为默认的实现,然后实际使用的类可以从抽象类派生。如果一个类实现了多接口,那我们只能选择一个抽象类作为祖先类,再将其他接口的实现手工加到类中。

    这种情况在C#3.0中有了变化,我们现在可以利用C#3.0的扩展方法来实现一个“受限的多继承”。

    C#3.0中引入了扩展方法,可以利用一个静态类的静态方法为一个类或者接口添加方法,关键是添加的方法是包含实现的,由此我们可以在C#3.0中为接口提供一个带实现的方法声明,而无需额外的实现类!如果一个类实现了多个这样的接口,就可以达到类似多继承的效果。

    让我们用代码测试一下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test35
{
    
public interface ITestA{ }

    
public static class ITestAHelper
    
{
        
public static void TestA(this ITestA obj)
        
{
            Console.WriteLine(
"ITestAHelper.TestA");
        }

    }


    
public interface ITestB{ }

    
public static class ITestBHelper
    
{
        
public static void TestB(this ITestB obj)
        
{
            Console.WriteLine(
"ITestBHelper.TestB");
        }

    }


    
public class Test : ITestA, ITestB
    
{
    }


    
class Program
    
{
        
static void Main(string[] args)
        
{
            Test obj1 
= new Test();
            obj1.TestA();
            obj1.TestB();

            Console.ReadKey();

        }

    }

}

    执行的结果:
   ITestAHelper.TestA     
   ITestBHelper.TestB
  
  


    ok!再多试一下,看看如果实现类中定义相同的方法会怎么样:

    public interface ITestA { }
    
public static class ITestAHelper
    
{
        
public static void TestA(this ITestA obj)
        
{
            Console.WriteLine(
"ITestAHelper.TestA");
        }

    }


    
public interface ITestB { }
    
public static class ITestBHelper
    
{
        
public static void TestB(this ITestB obj)
        
{
            Console.WriteLine(
"ITestBHelper.TestB");
        }

    }


    
public class Test : ITestA, ITestB
    
{
        
//此方法与ITestA的TestA()扩展方法相同
        public void TestA()
        
{
            Console.WriteLine(
"Test.TestA");
        }

    }


    
class Program
    
{
        
static void Main(string[] args)
        
{
            Test obj1 
= new Test();
            
//下面分别测试2种TestA()调用方式
              obj1.TestA();
            ((ITestA)obj1).TestA();

            Console.ReadKey();
        }

    }

   执行的结果:
   Test.TestA     
   ITestAHelper.TestA
 
 

    从这次的结果看,这里有一点点问题,如果实现类有相同的方法,接口的扩展方法总是被接口的实现类隐藏,那么如何多态?加上virtual试试看,似乎static方法是不能使用virtual修饰符的:

        public static virtual void TestA(this ITestA obj)
        
{
            Console.WriteLine(
"ITestAHelper.TestA");
        }

    编译出错,看来此路不通。

    再多试一下,如果接口中也定义相同的方法会这么样?

    public interface ITestA 
    
{
        
void TestA();
    }


    
public static class ITestAHelper
    
{
        
public static void TestA(this ITestA obj)
        
{
            Console.WriteLine(
"ITestAHelper.TestA");
        }

    }

   执行的结果:
   Test.TestA    
     Test.TestA 

    看来这样的话,扩展方法似乎总是被藏在接口的实现方法后面了,我没想出在接口变量中有什么方法可以访问到它。或许Reflection可以?我没有尝试下去。

    接口和类的方法声明可以和扩展方法相同,那一个类能不能实现2个拥有相同的扩展方法的接口呢?测试结果是编译错误,代码比较简单,有兴趣的朋友可以自己试试看。

    在此做个小小的总结,利用C#3.0的扩展方法,我们可以为接口提供默认的实现而无需定义一个实现类,如果一个类实现了多个这样的接口,就可以达到类似“多重继承”的效果。但是这种方法也有局限性,因为static方法不能使用virtual修饰符,所以接口的扩展方法只能被接口的实现类隐藏,而不能重写,无法实现多态的效果,这是个比较大的缺点。这点如果能解决,就完美了。没办法,这个世界没有完美的东西啊。

   

  

Tag标签: C#3.0
posted @ 2008-03-24 19:48 在路上的牛 阅读(2003) 评论(14)  编辑 收藏 所属分类: C#语言

  回复  引用  查看    
#1楼 2008-03-24 20:44 | Dflying Chen      
我们在系统设计时经常会抽象出一些接口,并为接口提供一个抽象类作为默认的实现,然后实际使用的类可以从抽象类派生。
----------
为什么要这样做呢?能详细说明一下么?
  回复  引用  查看    
#2楼 [楼主]2008-03-24 21:27 | 在路上的牛      
@Dflying Chen
因为很多时候,对接口的方法我们是可以提供一个默认的实现的,如果不提供这样一个抽象类,那每个实现接口的类都要写上许多一样的方法实现,是个不好的设计。System.Data下的许多类都是按照接口 - 抽象类 - 具体类的方式设计的,比如IDbCommand - DbCommand - SqlCommond/ODBCCommand/OLECommand
再比如在ORM框架中,我们可以为所有的DataObject设计1个Interface,里面可能有基本的DataState, AccpetChange(),Original...,接下来也会提供一个抽象类来实现这些方法,实际的业务对象从此抽象类派生,而不是每个业务对象都为这些功能写一套一样的实现代码。
  回复  引用  查看    
#3楼 2008-03-24 21:31 | lovecherry      
调用类的方法时编译器先从类中找,找不到再找扩展方法,再找不到就编译错误。
  回复  引用  查看    
#4楼 2008-03-24 21:36 | 坏人      
呵呵,但是脱离OO的继承,已经很大程度失去了原有的意义嘛.

目前对扩展方法持谨慎态度,怕会导致一些不必要的混乱..
  回复  引用  查看    
#5楼 [楼主]2008-03-24 22:43 | 在路上的牛      
这个方法是有很多局限。而且扩展方法是容易把人搞混,当初看Linq的代码,一个接口的实现方法半天没找到,后来才想起可能会在某个静态类的扩展方法中。。。
  回复  引用  查看    
#6楼 2008-03-24 22:56 | Angel Lucifer      
说实话,还真没用到过多继承,不过楼主给出了一个思路,Thanks。
  回复  引用  查看    
#7楼 2008-03-25 00:27 | Cat Chen      
@在路上的牛
你自己已经说出了,为什么不应该在接口上做默认实现。Framework Design Guideline说明了,建议的做法正式抽象的DbCommand实现IDbCommand,再派生出子类。不应该尝试直接在IDbCommand上面添加默认实现。
  回复  引用  查看    
#8楼 2008-03-25 01:25 | 曲滨*銘龘鶽      
还是伪 多集成;
添加的这个东西和

public static void TestB(this ITestB obj)

public static void TestB(ITestB obj)

原则上没啥两样、就是用着舒服了一点
用多了看代码时候迷糊,尤其是看别人的代码的时候
就和js似的、别人动态在那个对象上加了个东西有时候找半天

不过在VS里基本没这问题;有图标可以看见
  回复  引用  查看    
#9楼 2008-03-25 07:34 | 金色海洋(jyk)      
同意楼上。
  回复  引用  查看    
#10楼 2008-03-25 09:04 | xeme009 [未注册用户]
不知所云,还C# 3.0,C#1.0就可以做到,典型的标题党
  回复  引用  查看    
#11楼 2008-03-25 09:39 | dali [未注册用户]
楼主的思路绝对创新
  回复  引用  查看    
#12楼 2008-03-25 09:50 | henry      
我们在系统设计时经常会抽象出一些接口,并为接口提供一个抽象类作为默认的实现,然后实际使用的类可以从抽象类派生。

很大程度上是减低二次扩展的复杂性,接口是一个纯约束对外的规则,并不存在任何的实现。实现抽象类作为默认可以提供后期扩展内部需要的很多功能。

  回复  引用  查看    
#13楼 [楼主]2008-03-25 09:56 | 在路上的牛      
@Cat Chen
在接口上提供默认实现一般情况下并不需要,不应该滥用,但是这种方式在某些情况下是有价值的。
微软自己也有这样的例子,System.Linq中的IQueryable<T>就是一例,它自己没有声明任何方法,但是在Queryable类中为IQueryable<T>提供了大量的扩展方法。我自己也感觉以前的框架中某些接口是可以使用一些扩展方法的。
使用这样的方法,需要对接口的设计有比较全面的考虑。

  回复  引用  查看    
#14楼 2008-03-25 12:45 | 中华鹰      
多重继承感觉并没必要,这对于减少代码的混乱有很大帮助,我想微软也是出于这方面的考虑才不在C#中实现多重继承的吧。

所以,再用变相的方法实现多重继承,我觉得更没必要。

如果确实有必要,是不是可以通过某些设计模式来解决呢?

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交