是否可以通过显式类型转换将基类对象分配给派生类引用?


Answers:


98

否。对派生类的引用实际上必须引用派生类的实例(或null)。否则,您会期望它如何表现?

例如:

object o = new object();
string s = (string) o;
int i = s.Length; // What can this sensibly do?

如果希望将基本类型的实例转换为派生类型,建议您编写一种方法来创建适当的派生类型实例。或再次查看您的继承树,然后尝试重新设计,这样您就不需要一开始就这样做。


72
@Mike:代码可以很好地编译。尽管它在执行时
失败了

1
然后,当我们编写Base b = new Derived()时会发生什么??它将为基类和派生类创建对象吗?
Ashif Nataliya 2014年

3
@Akie:不,它创建一个类型为的单个对象Derived,但是您可以将Derived引用视为Base引用。
乔恩·斯基特

那么这两个语句的结果对象有什么区别?基数b =新的Base()和基数b =新的Derived()?互相使用有什么好处?
Ashif Nataliya 2014年

4
@Akie:是的,一个创建了的实例Base,另一个创建了的实例Derived。如果您调用了b被覆盖的虚拟方法,如果您有一个实例Derived,您将看到Derived行为Derived。但是,在Stack Overflow注释线程中进行详细讨论并不是真正合适的-您应该阅读一本不错的C#书或教程,因为这是非常基础的东西。
乔恩·斯基特

46

不,这是不可能的,因为将其分配给派生类引用就像是说“基类可以完全替代派生类,它可以完成派生类可以做的所有事情”,这是不正确的,因为派生类通常都提供比其基类更多的功能(至少,继承的思想)。

您可以在派生类中使用基类对象作为参数编写构造函数,并复制值。

像这样:

public class Base {
    public int Data;

    public void DoStuff() {
        // Do stuff with data
    }
}

public class Derived : Base {
    public int OtherData;

    public Derived(Base b) {
        this.Data = b.Data;
        OtherData = 0; // default value
    }

    public void DoOtherStuff() {
        // Do some other stuff
    }
}

在这种情况下,您将复制基础对象并获得具有派生成员默认值的功能齐全的派生类对象。这样,您还可以避免Jon Skeet指出的问题:

Base b = new Base();//base class
Derived d = new Derived();//derived class

b.DoStuff();    // OK
d.DoStuff();    // Also OK
b.DoOtherStuff();    // Won't work!
d.DoOtherStuff();    // OK

d = new Derived(b);  // Copy construct a Derived with values of b
d.DoOtherStuff();    // Now works!

23

我遇到了这个问题,并通过添加采用类型参数并将当前对象转换为该类型的方法来解决了该问题。

public TA As<TA>() where TA : Base
{
    var type = typeof (TA);
    var instance = Activator.CreateInstance(type);

     PropertyInfo[] properties = type.GetProperties();
     foreach (var property in properties)
     {
         property.SetValue(instance, property.GetValue(this, null), null);
     }

     return (TA)instance;
}

这意味着您可以像下面这样在代码中使用它:

var base = new Base();
base.Data = 1;
var derived = base.As<Derived>();
Console.Write(derived.Data); // Would output 1

您应该使用当前类(基类)的类型来获取和设置属性,因为这些是您要映射到派生类的值。
Bowofola

1
如果您具有无法以派生类型写入的属性,则应更改为:if(property.CanWrite)property.SetValue(instance,property.GetValue(this,null),null);
user3478586 '17

10

正如许多其他人回答的那样,不。

在那些需要使用基本类型作为派生类型的不幸情况下,我使用以下代码。是的,这违反了《里斯科夫换人原则》(LSP),是的,在大多数情况下,我们更倾向于采用组合而不是继承。对Markus Knappen Johansson的支持,其原始答案基于此。

此代码在基类中:

    public T As<T>()
    {
        var type = typeof(T);
        var instance = Activator.CreateInstance(type);

        if (type.BaseType != null)
        {
            var properties = type.BaseType.GetProperties();
            foreach (var property in properties)
                if (property.CanWrite)
                    property.SetValue(instance, property.GetValue(this, null), null);
        }

        return (T) instance;
    }

允许:

    derivedObject = baseObect.As<derivedType>()

由于它使用反射,因此是“昂贵的”。相应地使用。


我只是尝试了一下,并认为可以通过重载显式运算符(以及隐式运算符)来进一步改进。但是-编译器不允许这样做:user-defined conversions to or from a base class are not allowed 我知道这样做的原因,但感到失望,如果允许的话会很有趣
。– Henrik

@MEC:我注意到您删除了where T:MyBaseClass部分,并添加了if (type.BaseType != null)与Markus Knappen Johansson的A相关的语句。为什么?这意味着它将允许非MyBaseClass派生的调用中的类型(或与此有关的任何内容)。我意识到,如果将其分配给myDerivedObject,它仍然会导致编译器错误,但是,如果仅将其用作Expression,它将进行编译,并且在运行时只需创建myDerivedObject,而不会从“ myBaseObject”复制任何数据。我无法想象一个用例。
汤姆(Tom)

@Tom,回复较晚,但认为它可能仍然有用。对您的问题的最佳答案可能是说名称“ As”最好是“ AsOrDefault”。本质上,我们可以像使用Linq的SingleOrDefault或FirstOrDefault一样,将结果与Default进行比较。
MEC

7

不,这是不可能的,因此会导致运行时错误。

但是您可以将派生类的实例分配给基类类型的变量。


7

使用JsonConvert的解决方案(而不是类型转换)

今天,我遇到了同样的问题,并且使用找到了解决该问题的简便方法JsonConvert

var base = new BaseClass();
var json = JsonConvert.SerializeObject(base);
DerivedClass derived = JsonConvert.DeserializeObject<DerivedClass>(json);

我在下面用扩展方法再次回答了这个问题。是的,这就是答案。
Patrick Knott

5

正如这里的所有人所说,这不可能直接实现。

我更喜欢并且很干净的方法是使用像AutoMapper这样的对象映射器。

它将执行自动将属性从一个实例复制到另一个实例(不一定是相同类型)的任务。


3

扩展@ybo的答案-这是不可能的,因为您拥有的基类的实例实际上不是派生类的实例。它仅了解基类的成员,而对派生类的成员一无所知。

之所以可以将派生类的实例转换为基类的实例,是因为派生类实际上已经是基类的实例,因为它已经具有这些成员。相反不能说。


3

您可以将类型为基类的变量强制转换为派生类的类型。但是,必须进行运行时检查,以查看所涉及的实际对象是否具有正确的类型。

创建对象后,就无法更改其类型(尤其是大小可能不同)。但是,您可以转换一个实例,以创建第二种类型的实例-但您需要手动编写转换代码。


2

不,不可能。

考虑一个ACBus是基类Bus的派生类的情况。ACBus具有在名为ACState的字段上运行的TurnOnAC和TurnOffAC之类的功能。TurnOnAC将ACState设置为打开,TurnOffAC将ACState设置为关闭。如果尝试在总线上使用TurnOnAC和TurnOffAC功能,则没有任何意义。


2
class Program
{
    static void Main(string[] args)
    {
        a a1 = new b();  
        a1.print();  
    }
}
class a
{
    public a()
    {
        Console.WriteLine("base class object initiated");
    }
    public void print()
    {
        Console.WriteLine("base");
    }
}
class b:a
{
    public b()
    {
        Console.WriteLine("child class object");
    }
    public void print1()
    {
        Console.WriteLine("derived");
    }
}

}

创建子类对象时,基类对象会自动启动,因此基类引用变量可以指向子类对象。

但是反之亦然,因为子类引用变量不能指向基类对象,因为没有创建子类对象,因此反之亦然。

还要注意,基类引用变量只能调用基类成员。


2

实际上,有一种方法可以做到这一点。考虑一下如何使用Newtonsoft JSON从JSON反序列化对象。它会(或至少可以)忽略缺少的元素,并填充它确实知道的所有元素。

所以这就是我的做法。我的解释将遵循一小段代码示例。

  1. 从基类创建对象的实例,并相应地填充它。

  2. 使用Newtonsoft json的“ jsonconvert”类,将该对象序列化为json字符串。

  3. 现在,使用在步骤2中创建的json字符串反序列化来创建子类对象。这将创建具有基类所有属性的子类实例。

这就像一个魅力!所以..什么时候有用?有人问这在什么时候有意义,并建议更改OP的架构,以适应您不能通过类继承(在.Net中)本地完成的事实。

就我而言,我有一个设置类,其中包含服务的所有“基本”设置。特定服务具有更多选项,而这些选项来自不同的数据库表,因此这些类继承基类。它们都有不同的选择集。因此,当检索服务的数据时,使用基础对象的实例首先填充值要容易得多。一种使用单个数据库查询执行此操作的方法。之后,我使用上面概述的方法创建子类对象。然后,我进行第二次查询,并在子类对象上填充所有动态值。

最终输出是带有所有选项集的派生类。对其他新的子类重复此操作仅需几行代码。它很简单,并且使用了经过反复测试和测试的软件包(Newtonsoft)使魔术发挥作用。

此示例代码为vb.Net,但您可以轻松转换为c#。

' First, create the base settings object.
    Dim basePMSettngs As gtmaPayMethodSettings = gtmaPayments.getBasePayMethodSetting(payTypeId, account_id)
    Dim basePMSettingsJson As String = JsonConvert.SerializeObject(basePMSettngs, Formatting.Indented)

    ' Create a pmSettings object of this specific type of payment and inherit from the base class object
    Dim pmSettings As gtmaPayMethodAimACHSettings = JsonConvert.DeserializeObject(Of gtmaPayMethodAimACHSettings)(basePMSettingsJson)

使用C#和Newtonsoft.Json :var destObject = JsonConvert.DeserializeObject<DestinationType>(JsonConvert.SerializeObject(srcObject));。我只会将此用于单元测试和其他非生产性的“黑客攻击”!
thinkOfaNumber

2

您可以使用扩展名:

public static void CopyOnlyEqualProperties<T>(this T objDest, object objSource) where T : class
    {
        foreach (PropertyInfo propInfo in typeof(T).GetProperties())
            if (objSource.GetType().GetProperties().Any(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()))
                propInfo.SetValue(objDest, objSource.GetType().GetProperties().First(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()).GetValue(objSource));
    }

在代码中:

public class BaseClass
{
  public string test{ get; set;}
}
public Derived : BaseClass
{
//Some properies
}

public void CopyProps()
{
   BaseClass baseCl =new BaseClass();
   baseCl.test="Hello";
   Derived drv=new Derived();
   drv.CopyOnlyEqualProperties(baseCl);
   //Should return Hello to the console now in derived class.
   Console.WriteLine(drv.test);

}

1

可能不是无关紧要的,但是我能够在给定对象基础的情况下在派生对象上运行代码。绝对比我想要的更hacky,但是可以用:

public static T Cast<T>(object obj)
{
    return (T)obj;
}

...

//Invoke parent object's json function
MethodInfo castMethod = this.GetType().GetMethod("Cast").MakeGenericMethod(baseObj.GetType());
object castedObject = castMethod.Invoke(null, new object[] { baseObj });
MethodInfo jsonMethod = baseObj.GetType ().GetMethod ("ToJSON");
return (string)jsonMethod.Invoke (castedObject,null);

1

您可以使用泛型来执行此操作。

public class BaseClass
{
    public int A { get; set; }
    public int B { get; set; }
    private T ConvertTo<T>() where T : BaseClass, new()
    {
         return new T
         {
             A = A,
             B = B
         }
    }

    public DerivedClass1 ConvertToDerivedClass1()
    {
         return ConvertTo<DerivedClass1>();
    }

    public DerivedClass2 ConvertToDerivedClass2()
    {
         return ConvertTo<DerivedClass2>();
    }
}

public class DerivedClass1 : BaseClass
{
    public int C { get; set; }
}

public class DerivedClass2 : BaseClass
{
    public int D { get; set; }
}

使用这种方法,您将获得三个好处。

  1. 您没有复制代码
  2. 您没有使用反射(速度很慢)
  3. 您所有的转换都集中在一处

1

我知道这很旧,但是我已经成功使用了一段时间了。

   private void PopulateDerivedFromBase<TB,TD>(TB baseclass,TD derivedclass)
    {
        //get our baseclass properties
        var bprops = baseclass.GetType().GetProperties();
        foreach (var bprop in bprops)
        {
            //get the corresponding property in the derived class
            var dprop = derivedclass.GetType().GetProperty(bprop.Name);
            //if the derived property exists and it's writable, set the value
            if (dprop != null && dprop.CanWrite)
                dprop.SetValue(derivedclass,bprop.GetValue(baseclass, null),null);
        }
    } 

1

我结合了先前答案的某些部分(这要感谢那些作者),并将一个简单的静态类与我们使用的两种方法放在一起。

是的,这很简单,不是,它不能涵盖所有情况,是的,它可以扩展并做得更好,不是,它不是完美的,是的,它可以提高效率,是的,这不是自切面包以来最伟大的事情,是的,全面的,健壮的nuget包对象映射器,对于大量使用等来说更好,等等,yada yada-但它可以满足我们的基本需求:)

当然,它将尝试将值从任何对象映射到任何对象(无论是否派生)(当然,只有名称相同的公共属性-忽略其余属性)。

用法:

SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 };

// creates new object of type "RealPerson" and assigns any matching property 
// values from the puppet object 
// (this method requires that "RealPerson" have a parameterless constructor )
RealPerson person = ObjectMapper.MapToNewObject<RealPerson>(puppet);

// OR

// create the person object on our own 
// (so RealPerson can have any constructor type that it wants)
SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 };
RealPerson person = new RealPerson("tall") {Name = "Steve"};

// maps and overwrites any matching property values from 
// the puppet object to the person object so now our person's age will get set to 5 and
// the name "Steve" will get overwritten with "Elmo" in this example
ObjectMapper.MapToExistingObject(puppet, person);

静态实用程序类:

public static class ObjectMapper
{
    // the target object is created on the fly and the target type 
    // must have a parameterless constructor (either compiler-generated or explicit) 
    public static Ttarget MapToNewObject<Ttarget>(object sourceobject) where Ttarget : new()
    {
        // create an instance of the target class
        Ttarget targetobject = (Ttarget)Activator.CreateInstance(typeof(Ttarget));

        // map the source properties to the target object
        MapToExistingObject(sourceobject, targetobject);

        return targetobject;
    }

    // the target object is created beforehand and passed in
    public static void MapToExistingObject(object sourceobject, object targetobject)
    {
        // get the list of properties available in source class
        var sourceproperties = sourceobject.GetType().GetProperties().ToList();

        // loop through source object properties
        sourceproperties.ForEach(sourceproperty => {

            var targetProp = targetobject.GetType().GetProperty(sourceproperty.Name);

            // check whether that property is present in target class and is writeable
            if (targetProp != null && targetProp.CanWrite)
            {
                // if present get the value and map it
                var value = sourceobject.GetType().GetProperty(sourceproperty.Name).GetValue(sourceobject, null);
                targetobject.GetType().GetProperty(sourceproperty.Name).SetValue(targetobject, value, null);
            }
        });
    }
}

1

您可以使用立即调用实例构造函数的副本构造函数,或者如果您的实例构造函数所做的不只是分配,则副本构造函数会将传入的值分配给实例。

class Person
{
    // Copy constructor 
    public Person(Person previousPerson)
    {
        Name = previousPerson.Name;
        Age = previousPerson.Age;
    }

    // Copy constructor calls the instance constructor.
    public Person(Person previousPerson)
        : this(previousPerson.Name, previousPerson.Age)
    {
    }

    // Instance constructor.
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public int Age { get; set; }

    public string Name { get; set; }
}

对于此示例,过去曾遇到此问题,在构造函数下引用了Microsoft C#文档


0

另一个解决方案是添加扩展方法,如下所示:

 public static void CopyProperties(this object destinationObject, object sourceObject, bool overwriteAll = true)
        {
            try
            {
                if (sourceObject != null)
                {
                    PropertyInfo[] sourceProps = sourceObject.GetType().GetProperties();
                    List<string> sourcePropNames = sourceProps.Select(p => p.Name).ToList();
                    foreach (PropertyInfo pi in destinationObject.GetType().GetProperties())
                    {
                        if (sourcePropNames.Contains(pi.Name))
                        {
                            PropertyInfo sourceProp = sourceProps.First(srcProp => srcProp.Name == pi.Name);
                            if (sourceProp.PropertyType == pi.PropertyType)
                                if (overwriteAll || pi.GetValue(destinationObject, null) == null)
                                {
                                    pi.SetValue(destinationObject, sourceProp.GetValue(sourceObject, null), null);
                                }
                        }
                    }
                }
            }
            catch (ApplicationException ex)
            {
                throw;
            }
        }

然后在每个接受基类的派生类中都有一个构造函数:

  public class DerivedClass: BaseClass
    { 
        public DerivedClass(BaseClass baseModel)
        {
            this.CopyProperties(baseModel);
        }
    }

如果已经设置(不为null),则它还将有选择地覆盖目标属性。


0

是否可以在C#中使用显式类型转换将基类对象分配给派生类引用?

不仅可以进行显式转换,还可以进行隐式转换。

C#语言不允许使用此类转换运算符,但是您仍然可以使用纯C#编写它们,并且它们可以工作。请注意,定义隐式转换运算符(Derived)的类和使用该运算符(Program)的类必须在单独的程序集中定义(例如,Derived该类位于,library.dll通过program.exe包含Program该类被引用)。

//In library.dll:
public class Base { }

public class Derived {
    [System.Runtime.CompilerServices.SpecialName]
    public static Derived op_Implicit(Base a) {
        return new Derived(a); //Write some Base -> Derived conversion code here
    }

    [System.Runtime.CompilerServices.SpecialName]
    public static Derived op_Explicit(Base a) {
        return new Derived(a); //Write some Base -> Derived conversion code here
    }
}

//In program.exe:
class Program {
    static void Main(string[] args) {
        Derived z = new Base(); //Visual Studio can show squiggles here, but it compiles just fine.
    }
}

当您使用Visual Studio中的“项目引用”来引用库时,使用隐式转换时,VS会显示出花样,但是编译起来还不错。如果仅参考library.dll,则没有花键。


这是什么黑魔法?!?另外,“ Derived z = new Base()”如何帮助我做“ BaseCls baseObj; DerivedCls namedObj; derivedObj =(DerivedCls)baseObj”(OP的Q)?另外,System.Runtime.CompilerServices.SpecialName属性有什么作用?从最早的可用版本(2.0)到“当前版本”(4.6?“任何人?任何人?”)的每个版本的文档都没有说明其作用,而是说“ .NET中当前未使用SpecialNameAttribute类。框架,但保留以备将来使用。”。请参阅:[链接](msdn.microsoft.com/zh-cn/library/ms146064 ( v=vs.100 ) .aspx)。
汤姆(Tom)

>“这是什么黑魔法?!?” 这就是所谓的.Net Framework(CLR,IL,BCL)。IL,C#和VB语言的功能集不相同。VB中有C#不支持的功能。IL中有C#不支持的功能。C#中存在一些相当随意的限制,并且在底层IL中不存在限制(例如类似where T : Delegate或参数化的属性,也就是索引器等)。
方舟坤

>“此外,“派生的z = new Base()”如何帮助我执行“ BaseCls baseObj; DerivedCls派生的Obj;namedObj =(DerivedCls)baseObj“(OP的Q)?” 就是这样。它解决了OP的问题。而且您甚至不需要显式强制转换。
方舟坤

> what does System.Runtime.CompilerServices.SpecialName Attribute do?-它用于标记由高级.Net语言的某些特殊便利构造产生的方法:属性访问器,事件访问器,构造函数,运算符,索引器等。除非用IL方法标记,specialname否则它将看不到作为属性/事件/构造函数,它将仅被视为常规方法。使用此属性手动标记名称正确的方法只是手动完成编译器的工作。
方舟坤

VB.Net具有电源操作员。C#没有。您如何在C#中重载Power Operator以便在VB.Net中使用?只需定义一个op_Exponent方法并用specialname属性标记即可。
方舟坤

0

怎么样:

public static T As<T>(this object obj)
    {
        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj));
    }

0

将所有基本属性添加到派生项的最佳方法是在构造函数中使用反射。尝试使用此代码,而不创建方法或实例。

    public Derived(Base item) :base()
    {

        Type type = item.GetType();

        System.Reflection.PropertyInfo[] properties = type.GetProperties();
        foreach (var property in properties)
        {
            try
            {
                property.SetValue(this, property.GetValue(item, null), null);
            }
            catch (Exception) { }
        }

    }

0

我不同意这是不可能的。您可以这样做:

public class Auto 
{ 
    public string Make {get; set;}
    public string Model {get; set;}
}

public class Sedan : Auto
{ 
    public int NumberOfDoors {get; set;}
}

public static T ConvertAuto<T>(Sedan sedan) where T : class
{
    object auto = sedan;
    return (T)loc;
}

用法:

var sedan = new Sedan();
sedan.NumberOfDoors = 4;
var auto = ConvertAuto<Auto>(sedan);

var auto =仍然是类型sedan
杰科

0

这就是我为字段解决的方法。如果需要,可以通过属性进行相同的迭代。您可能需要对等进行一些检查,null但这就是这个主意。

 public static DerivedClass ConvertFromBaseToDerived<BaseClass, DerivedClass>(BaseClass baseClass)
            where BaseClass : class, new()
            where DerivedClass : class, BaseClass, new()
        {
            DerivedClass derived = (DerivedClass)Activator.CreateInstance(typeof(DerivedClass));
            derived.GetType().GetFields().ToList().ForEach(field =>
            {
                var base_ = baseClass.GetType().GetField(field.Name).GetValue(baseClass);
                field.SetValue(derived, base_);

            });

            return derived;
        }


0

不在传统意义上...转换为Json,然后转换为您的对象,然后完成!上面的Jesse首先发布了答案,但没有使用这些扩展方法,这使过程变得如此容易。创建几个扩展方法:

    public static string ConvertToJson<T>(this T obj)
    {
        return JsonConvert.SerializeObject(obj);
    }
    public static T ConvertToObject<T>(this string json)
    {
        if (string.IsNullOrEmpty(json))
        {
            return Activator.CreateInstance<T>();
        }
        return JsonConvert.DeserializeObject<T>(json);
    }

将它们永久放在您的工具箱中,然后您就可以始终这样做:

var derivedClass = baseClass.ConvertToJson().ConvertToObject<derivedClass>();

啊,JSON的力量。

这种方法有两个陷阱:实际上,我们正在创建一个新对象,而不是强制转换,这可能会或可能不会重要。私有字段将不会被传输,带有参数的构造函数将不会被调用,等等。有可能不会分配一些子json。流不是由JsonConvert天生处理的。但是,如果我们的类不依赖私有字段和构造函数,那么这是一种非常有效的方法,可以在不映射和调用构造函数的情况下将数据从类移动到类,这是我们首先要进行转换的主要原因。


这不符合OP的要求。您正在做的是使用错误类型的原始对象中的数据为变量构造正确类型的新对象。这可能会或可能不会,但无论哪种方式,都肯定不会将基类类型的对象分配给派生类型的变量。
Lasse V. Karlsen

我回答了这个问题:是否可以通过显式类型转换将基类对象分配给派生类引用?说不。我提供了一个绝对可行的替代方法,并且没有泛型那么令人困惑。正如上面多次指出的那样,它可能会导致从基类分配给派生类属性的问题,但是,如果可能的话,这正是它的工作方式(并且在api中也是如此)。仅仅因为我的答案可以用于“错误”类型,并不表示它不能用于“正确”类型。@ LasseV.Karlsen,请撤消您的负面评价。
Patrick Knott

与菊花链JsonConverts给出的大多数答案不同,我还展示了如何处理null。
Patrick Knott

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.