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

问题描述 投票:72回答:22

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

我试过它,它会产生运行时错误。

c# explicit derived-class casting base-class
22个回答
86
投票

否。对派生类的引用实际上必须引用派生类的实例(或null)。否则你会怎么期望它表现出来?

例如:

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

如果您希望能够将基类型的实例转换为派生类型,我建议您编写一个方法来创建适当的派生类型实例。或者再次查看继承树并尝试重新设计,这样您就不需要首先执行此操作。


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中)本地执行此操作。

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

最终输出是一个派生类,其中设置了所有选项。对于其他新的子类重复此操作只需几行代码。它很简单,它使用经过严格测试的软件包(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)

1
投票

您可以使用扩展名:

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
投票

我知道这已经老了,但我已成功使用了这段时间了。

   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
投票

Solution with JsonConvert (instead of typecast)

今天我面临同样的问题,我发现使用JsonConvert解决问题的简单快速。

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

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

如果已经设置(非空),它还可以选择覆盖目标属性。


0
投票

可能不相关,但我能够在派生对象的基础上运行代码。它肯定比我想的更开心,但它有效:

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

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中的Project Reference引用库时,VS会在您使用隐式转换时显示波形,但它编译得很好。如果你只是参考library.dll,就没有曲线。


0
投票

你可以使用泛型来做到这一点。

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. 您的所有转化都在一个地方

0
投票

我结合了之前答案的一些部分(感谢那些作者)并将一个简单的静态类与我们正在使用的两个方法组合在一起。

是的,它很简单,没有它不能涵盖所有场景,是的它可以扩展并变得更好,不是它不完美,是的它可能会变得更有效率,不是它不是最好的东西,因为切片面包,是的有全面的强大的nuget包对象mappers在那里,更好的大量使用等等,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);
            }
        });
    }
}

41
投票

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

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

像这样的东西:

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();
Dervided d = new Derived();

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!

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

    }

-1
投票

不,看到我问过的这个问题 - Upcasting in .NET using generics

最好的方法是在类上创建一个默认构造函数,构造然后调用一个Initialise方法


19
投票

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

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

10
投票

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

当我需要使用基类型作为派生类型时,我在那些不幸的场合使用以下代码。是的,它违反了Liskov替代原则(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>()

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


5
投票

不,这是不可能的,因此您的运行时错误。

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


4
投票

正如大家在这里所说,这是不可能直接的。

我更喜欢并且相当干净的方法是使用像AutoMapper这样的Object Mapper。

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


3
投票

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

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


2
投票

扩展@ ybo的答案 - 这是不可能的,因为你拥有基类的实例实际上并不是派生类的实例。它只知道基类的成员,并且对派生类的成员一无所知。

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


2
投票

不,这是不可能的。

考虑ACBus是基类Bus的派生类的场景。 ACBus具有TurnOnAC和TurnOffAC等功能,可在名为ACState的字段上运行。 TurnOnAC将ACState设置为on,TurnOffAC将ACState设置为off。如果您尝试在总线上使用TurnOnAC和TurnOffAC功能,则没有任何意义。

© www.soinside.com 2019 - 2024. All rights reserved.