ไม่สามารถแคสต์จากคลาสผู้ปกครองไปยังคลาสย่อยได้


104

ฉันพยายามแคสต์จากคลาสพาเรนต์ไปยังคลาสย่อย แต่ฉันได้รับ InvalidCastException คลาสย่อยมีคุณสมบัติประเภท int เพียงอย่างเดียว มีใครรู้บ้างว่าฉันต้องทำอย่างไร?


นอกจากนี้ยังควรทราบด้วยว่าคุณไม่สามารถใช้การแคสต์อย่างชัดเจนสำหรับคลาสที่เกี่ยวข้องกับพื้นฐาน / การได้รับ
Rzassar

คำตอบ:


137

วิธีง่ายๆในการดาวน์แคสต์ใน C # คือการทำให้เป็นอนุกรมพาเรนต์จากนั้นจึงแยกสายเข้ากับลูก

 var serializedParent = JsonConvert.SerializeObject(parentInstance); 
 Child c  = JsonConvert.DeserializeObject<Child>(serializedParent);

ฉันมีแอปคอนโซลง่ายๆที่ทำให้สัตว์กลายเป็นสุนัขโดยใช้โค้ดสองบรรทัดด้านบนตรงนี้


22
ฉันจะลังเลที่จะเรียกสิ่งนี้ว่า "ดาวน์แคสต์"
Kirk Woll

โปรดทราบว่าชื่อตัวแปรไม่เหมือนกันข้างต้น
Jake Gaston

7
ฉันชอบเวลาที่มีคนคิดนอกกรอบและปิดปากคนที่บอก OP ว่าทำไม่ได้ (บันทึกไว้หนึ่งหรือสองโทรลล์)! ขอบคุณสำหรับความช่วยเหลือในเรื่องนี้ ฉันพยายามคิดเรื่องนี้มาหลายชั่วโมงแล้ว :)
derekmx271

3
นี่เป็นทางออกที่ยอดเยี่ยม ฉันมีกรณีที่คลาสลูกของฉันเป็นเพียงกระดาษห่อหุ้มสำหรับผู้ปกครองที่ไม่มีฟังก์ชันเพิ่มเติม ฉันทำเช่นนั้นฉันจึงไม่ต้องนำเข้าการอ้างอิงเว็บลงในแอปพลิเคชันของฉันเนื่องจากอยู่ในไลบรารีผู้ช่วยเหลือ สิ่งนี้ทำให้ฉันสามารถแปลงพาเรนต์เป็นคลาส Wrapper ของฉันได้ ขอบคุณ!
BrianVPS

1
คุณคืออัจฉริยะ! :)
Yablargo

122

คุณไม่สามารถเสกสัตว์เลี้ยงลูกด้วยนมให้กลายเป็นสุนัขได้มันอาจจะเป็นแมวก็ได้

คุณไม่สามารถโยนอาหารลงในแซนวิชได้ - อาจเป็นชีสเบอร์เกอร์

คุณไม่สามารถหล่อรถเป็นเฟอร์รารีได้ - อาจเป็นฮอนด้าหรือโดยเฉพาะอย่างยิ่งคุณไม่สามารถหล่อ Ferrari 360 Modena ไปยัง Ferrari 360 Challange Stradale ได้ - มีชิ้นส่วนที่แตกต่างกันแม้ว่าจะเป็น Ferrari 360 ทั้งคู่ก็ตาม


17
สิ่งกีดขวางบนถนนที่เข้าใจได้ดังนั้นจึงเป็นไปไม่ได้ที่จะ 'หล่อ' ในลักษณะนี้ แต่จะเป็นอย่างไรถ้าเขาต้องการสุนัขที่มีสีตา / น้ำหนัก / ขน / อายุเช่นเดียวกับแมวที่ถูกขังอยู่ในวัตถุเลี้ยงลูกด้วยนมล่ะ? คัดลอกคุณสมบัติทั่วไปเป็นหลัก
FastAl

7
FastAl นี่คือเหตุผลที่เรามีอินเทอร์เฟซ สัตว์เลี้ยงลูกด้วยนมต้องใช้ IMammal และมีสีตาน้ำหนักเป็นต้นตอนนี้คุณสามารถโยนทั้งสุนัขและแมวให้กับ IMammal
Tom Deloford

1
คุณสามารถลองแคสต์สัตว์เลี้ยงลูกด้วยนมเป็นสุนัข ถ้าเป็นหมาก็คือหมา มิฉะนั้นจะกลายเป็นโมฆะ ฟังก์ชัน "โอเวอร์โหลด" สามารถทำให้การแปลงจากแมวเป็นสุนัขเป็นไปไม่ได้เป็นไปได้หาก cat มีฟังก์ชันที่มากเกินไปเหล่านี้จะทำให้สามารถทำได้ แต่เป็นหน้าที่ของคุณในการจัดการกับการสูญเสียข้อมูลและปรับเปลี่ยนข้อมูลที่ไม่มีอยู่ ชอบแปลงก้ามปูเป็นตะปูไล่เชือกไล่บอล ฯลฯ ...
TamusJRoyce

ฉันคิดว่าตัวอย่างนั้นค่อนข้างสุดโต่งและมีการคัดเลือกและบางทีนักแสดงอาจเป็นทางลัดสำหรับตัวสร้างสำเนา ตัวอย่างเช่นกำลังสร้างเฟอร์รารีด้วยคุณสมบัติที่กำหนดไว้ในรถวัตถุพื้นฐาน หรือเริ่มต้นด้วยมนุษย์และสร้างเด็กชาย หล่อและใช้โดยตรง? ยอมรับว่าไม่ใช่ไม่ใช่ แต่ถ้าเป็นส่วนหนึ่งของตัวสร้างหรือบางสิ่งบางอย่างสามารถทำงานได้ คำตอบการทำให้เป็นอนุกรมด้านล่างเป็นสัมผัสที่ดี
sirthomas

1
Ferrari analogy NICE
Lord Darth Vader

59

อินสแตนซ์ที่การอ้างอิงคลาสพื้นฐานของคุณอ้างถึงไม่ใช่อินสแตนซ์ของคลาสย่อยของคุณ ไม่มีอะไรผิดปกติ

โดยเฉพาะอย่างยิ่ง:

Base derivedInstance = new Derived();
Base baseInstance = new Base();

Derived good = (Derived)derivedInstance; // OK
Derived fail = (Derived)baseInstance; // Throws InvalidCastException

เพื่อให้การแคสต์ประสบความสำเร็จอินสแตนซ์ที่คุณดาวน์คาสติ้งจะต้องเป็นอินสแตนซ์ของคลาสที่คุณกำลังดาวน์คาสติ้ง (หรืออย่างน้อยคลาสที่คุณดาวน์คาสติ้งจะต้องอยู่ในลำดับชั้นของอินสแตนซ์) มิฉะนั้น การร่ายจะล้มเหลว


หรืออาจเป็น Base otherDerived = OtherDerived ใหม่ (); ได้รับ otherFail = (ได้มา) otherDerived;
Blair Conrad

คลาส Base {} คลาสที่ได้รับ: ฐาน {} // ในเมธอดหลักที่ได้รับอินสแตนซ์ = ที่ได้รับใหม่ (); ฐาน baseInstance = ฐานใหม่ (); ดีที่ได้มา = (ที่ได้มา) มาจากอินสแตนซ์; ได้รับความล้มเหลว = (ได้มา) baseInstance; สิ่งนี้รวบรวมโดยไม่มีข้อผิดพลาดใด ๆ ใน. NET 3.5 ปัญหาที่คุณพูดอยู่ที่ไหน?
pradeeptp

8
@pradeeptp: แน่นอนมันสร้าง ใครพูดอะไรเกี่ยวกับข้อผิดพลาดในการคอมไพล์
Greg D

19

ฉันเคยเห็นคนส่วนใหญ่พูดว่าพ่อแม่ที่ชัดเจนต่อการคัดเลือกเด็กเป็นไปไม่ได้ซึ่งจริงๆแล้วไม่เป็นความจริง มาเริ่มต้นแก้ไขและลองพิสูจน์ด้วยตัวอย่าง

ดังที่เราทราบใน. net การแคสติ้งทั้งหมดมีสองประเภทกว้าง ๆ

  1. สำหรับประเภท Value
  2. สำหรับประเภทการอ้างอิง (ในกรณีของคุณเป็นประเภทการอ้างอิง)

ประเภทการอ้างอิงมีสถานการณ์หลักอีกสามกรณีที่สถานการณ์ใด ๆ สามารถโกหกได้

Child to Parent (การคัดเลือกโดยนัย - ประสบความสำเร็จเสมอ)

กรณีที่ 1.เด็กกับผู้ปกครองโดยตรงหรือโดยอ้อม

Employee e = new Employee();
Person p = (Person)e; //Allowed

Parent to Child (การแคสต์อย่างชัดเจน - สามารถทำได้สำเร็จ)

กรณีที่ 2.ตัวแปรหลักที่ถืออ็อบเจ็กต์พาเรนต์ (ไม่อนุญาต)

Person p = new Person();  // p is true Person object
Employee e = (Employee)p; //Runtime err : InvalidCastException <-------- Yours issue

กรณีที่ 3.ตัวแปรหลักถือวัตถุลูก (ประสบความสำเร็จเสมอ)

หมายเหตุ: เนื่องจากอ็อบเจ็กต์มีลักษณะที่หลากหลายจึงเป็นไปได้ที่ตัวแปรของประเภทคลาสพาเรนต์จะมีประเภทลูก

Person p = new Employee(); // p actually is Employee
Employee e = (Employee)p; // Casting allowed

สรุป:หลังจากอ่านข้างต้นแล้วหวังว่าตอนนี้มันจะสมเหตุสมผลเหมือนกับการเปลี่ยนพ่อแม่เป็นลูก (กรณีที่ 3)

ตอบคำถาม:

คำตอบของคุณคือในกรณีที่ 2 OOP ไม่อนุญาตให้แคสติ้งดังกล่าวและคุณพยายามละเมิดกฎพื้นฐานข้อใดข้อหนึ่งของ OOP ดังนั้นควรเลือกเส้นทางที่ปลอดภัยเสมอ

ยิ่งไปกว่านั้นเพื่อหลีกเลี่ยงสถานการณ์พิเศษเช่นนี้. net แนะนำให้ใช้คือ / เป็นตัวดำเนินการซึ่งจะช่วยคุณในการตัดสินใจอย่างมีข้อมูลและจัดเตรียมการหล่ออย่างปลอดภัย


ฉันชอบความชัดเจนของคำตอบของคุณ ปัญหาเดียวคือลิงก์สำหรับเอกสาร is / as ให้ 404 ลิงก์ต่อไปนี้จะเป็นสิ่งที่คุณอ้างอิงหรือไม่? พิมพ์ทดสอบและหล่อ
J Man

18

มีบางกรณีที่นักแสดงจะเข้าท่า
กรณีของฉันฉันได้รับคลาส BASE ผ่านเครือข่ายและฉันต้องการคุณสมบัติเพิ่มเติม ดังนั้นการได้รับมันมาเพื่อจัดการกับระฆังและนกหวีดทั้งหมดที่ฉันต้องการและการโยนคลาส BASE ที่ได้รับไปยัง DERIVED นั้นไม่ใช่ทางเลือก (โยน InvalidCastException ของหลักสูตร)

วิธีแก้ปัญหาแบบคิดนอกกรอบที่ใช้ได้จริงวิธีหนึ่งคือการประกาศคลาส EXTENSION Helper ที่ไม่ได้สืบทอดคลาส BASE จริง ๆ แต่รวมถึงการเป็นสมาชิกด้วย

public class BaseExtension
{
   Base baseInstance;

   public FakeDerived(Base b)
   {
      baseInstance = b;
   }

   //Helper methods and extensions to Base class added here
}

ถ้าคุณมีเพศสัมพันธ์หลวมและเพียงแค่ต้องคู่ของคุณสมบัติพิเศษในการชั้นฐานโดยไม่มีจริงๆมีความจำเป็นที่แน่นอนของมาว่าอาจจะเป็นวิธีแก้ปัญหาที่รวดเร็วและง่าย


ฉันคิดถูกหรือเปล่าว่าBaseExtensionอย่างน้อยคุณอาจต้องการให้คุณที่นี่นำไปIBaseใช้ในบริบทที่คล้ายกัน หรือไม่สำคัญสำหรับความต้องการของคุณ?
ถึง

บางครั้งรวมถึงอาจเป็นการทดแทนที่เหมาะสมสำหรับการสืบทอด
Vahid Ghadiri

13

นั่นจะเป็นการละเมิดหลักการเชิงวัตถุ ฉันจะบอกว่าโซลูชันที่สวยงามที่นี่และที่อื่น ๆ ในโครงการคือการใช้กรอบการทำแผนที่วัตถุเช่นAutoMapperเพื่อกำหนดค่าการฉายภาพ

นี่คือการกำหนดค่าที่ซับซ้อนกว่าที่จำเป็นเล็กน้อย แต่มีความยืดหยุ่นเพียงพอสำหรับกรณีส่วนใหญ่:

public class BaseToChildMappingProfile : Profile
{
    public override string ProfileName
    {
        get { return "BaseToChildMappingProfile"; }
    }

    protected override void Configure()
    {
        Mapper.CreateMap<BaseClass, ChildClassOne>();
        Mapper.CreateMap<BaseClass, ChildClassTwo>();
    }
}


public class AutoMapperConfiguration
{
    public static void Configure()
    {
        Mapper.Initialize(x =>
        {
            x.AddProfile<BaseToChildMappingProfile>();
        });
    }
}

เมื่อแอปพลิเคชันเริ่มโทรAutoMapperConfiguration.Configure()จากนั้นคุณสามารถฉายได้ดังนี้:

ChildClassOne child = Mapper.Map<BaseClass, ChildClassOne>(baseClass);

คุณสมบัติถูกแม็ปโดยแบบแผนดังนั้นหากคลาสได้รับการสืบทอดชื่อคุณสมบัติจะเหมือนกันทุกประการและการแม็ปจะถูกกำหนดค่าโดยอัตโนมัติ คุณสามารถเพิ่มคุณสมบัติเพิ่มเติมได้โดยปรับแต่งการกำหนดค่า ดูเอกสาร


การใช้ Automapper เพื่อแมปประเภทที่มีคุณสมบัติเดียวกับคุณสมบัติอื่น (เหมือนที่ OP อธิบายไว้) ก็เหมือนกับการใช้ค้อนเลื่อนเพื่อทุบไข่ ทำไมไม่สร้างประเภทที่ได้รับขึ้นมาใหม่และกำหนดคุณสมบัติด้วยตัวคุณเอง (ซึ่งก็คือโค้ด 1 บรรทัด)
bytedev

9

พอลคุณไม่ได้ถามว่า 'ทำได้ไหม' - ฉันสมมติว่าคุณอยากรู้วิธีทำ!

เราต้องทำสิ่งนี้ในโปรเจ็กต์ - มีคลาสมากมายที่เราตั้งขึ้นในแบบทั่วไปเพียงครั้งเดียวจากนั้นเริ่มต้นคุณสมบัติเฉพาะสำหรับคลาสที่ได้รับ ฉันใช้ VB ดังนั้นตัวอย่างของฉันจึงอยู่ใน VB (noogies ที่ยาก) แต่ฉันขโมยตัวอย่าง VB จากไซต์นี้ซึ่งมีเวอร์ชัน C # ที่ดีกว่าด้วย:

http://www.eggheadcafe.com/tutorials/aspnet/a4264125-fcb0-4757-9d78-ff541dfbcb56/net-reflection--copy-cl.aspx

โค้ดตัวอย่าง:

Imports System
Imports System.Collections.Generic
Imports System.Reflection
Imports System.Text
Imports System.Diagnostics

Module ClassUtils

    Public Sub CopyProperties(ByVal dst As Object, ByVal src As Object)
        Dim srcProperties() As PropertyInfo = src.GetType.GetProperties
        Dim dstType = dst.GetType

        If srcProperties Is Nothing Or dstType.GetProperties Is Nothing Then
            Return
        End If

        For Each srcProperty As PropertyInfo In srcProperties
            Dim dstProperty As PropertyInfo = dstType.GetProperty(srcProperty.Name)

            If dstProperty IsNot Nothing Then
                If dstProperty.PropertyType.IsAssignableFrom(srcProperty.PropertyType) = True Then
                    dstProperty.SetValue(dst, srcProperty.GetValue(src, Nothing), Nothing)
                End If
            End If
        Next
    End Sub
End Module


Module Module1
    Class base_class
        Dim _bval As Integer
        Public Property bval() As Integer
            Get
                Return _bval
            End Get
            Set(ByVal value As Integer)
                _bval = value
            End Set
        End Property
    End Class
    Class derived_class
        Inherits base_class
        Public _dval As Integer
        Public Property dval() As Integer
            Get
                Return _dval
            End Get
            Set(ByVal value As Integer)
                _dval = value
            End Set
        End Property
    End Class
    Sub Main()
        ' NARROWING CONVERSION TEST
        Dim b As New base_class
        b.bval = 10
        Dim d As derived_class
        'd = CType(b, derived_class) ' invalidcast exception 
        'd = DirectCast(b, derived_class) ' invalidcast exception
        'd = TryCast(b, derived_class) ' returns 'nothing' for c
        d = New derived_class
        CopyProperties(d, b)
        d.dval = 20
        Console.WriteLine(b.bval)
        Console.WriteLine(d.bval)
        Console.WriteLine(d.dval)
        Console.ReadLine()
    End Sub
End Module

เรื่องนี้ไม่หล่อจริงๆ เป็นการสร้างวัตถุที่ได้รับใหม่และคัดลอกคุณสมบัติจากพาเรนต์โดยปล่อยให้คุณสมบัติลูกว่างไว้ นั่นคือทั้งหมดที่ฉันต้องทำและดูเหมือนว่าคุณต้องทำทั้งหมด โปรดทราบว่าจะคัดลอกคุณสมบัติเท่านั้นไม่ใช่สมาชิก (ตัวแปรสาธารณะ) ในชั้นเรียน (แต่คุณสามารถขยายเพื่อทำเช่นนั้นได้หากคุณต้องการความอัปยศในการเปิดเผยสมาชิกสาธารณะ)

การแคสต์โดยทั่วไปจะสร้างตัวแปร 2 ตัวที่ชี้ไปที่วัตถุเดียวกัน (บทแนะนำเล็ก ๆ ที่นี่โปรดอย่าโยนข้อยกเว้นกรณีมุมใส่ฉัน) มีการแบ่งส่วนที่สำคัญสำหรับสิ่งนี้ (แบบฝึกหัดสำหรับผู้อ่าน)!

แน่นอนฉันต้องบอกว่าทำไมคนขี้เกียจไม่ยอมให้คุณไปจากฐานไปหาอินสแตนซ์ แต่ทำในทางอื่น ลองนึกภาพกรณีที่คุณสามารถนำอินสแตนซ์ของกล่องข้อความ winforms (ที่ได้มา) และเก็บไว้ในตัวแปรประเภทควบคุม Winforms แน่นอนว่า 'control' สามารถย้ายวัตถุไปรอบ ๆ OK และคุณสามารถจัดการกับ 'controll-y' ทุกอย่างเกี่ยวกับ textbox ได้ (เช่นคุณสมบัติด้านบนซ้าย. text) ไม่สามารถมองเห็นสิ่งที่เฉพาะเจาะจงของกล่องข้อความ (เช่น. multiline) ได้หากไม่ส่งตัวแปรประเภท 'ควบคุม' ที่ชี้ไปที่กล่องข้อความในหน่วยความจำ แต่ยังคงอยู่ในหน่วยความจำ

ตอนนี้ลองนึกภาพว่าคุณมีตัวควบคุมและคุณต้องการกำหนดตัวแปรประเภท textbox ให้กับมัน การควบคุมในหน่วยความจำไม่มี 'multiline' และ textboxy อื่น ๆ หากคุณพยายามอ้างอิงสิ่งเหล่านี้การควบคุมจะไม่เพิ่มคุณสมบัติหลายสายอย่างน่าอัศจรรย์! คุณสมบัติ (ดูที่มันเหมือนตัวแปรสมาชิกที่นี่ที่เก็บค่าจริง - เนื่องจากมีอยู่ในหน่วยความจำของอินสแตนซ์กล่องข้อความ) ต้องมีอยู่ เนื่องจากคุณกำลังแคสต์จำไว้ว่าต้องเป็นวัตถุเดียวกันกับที่คุณกำลังชี้ไป ดังนั้นจึงไม่ใช่ข้อ จำกัด ด้านภาษาจึงเป็นไปไม่ได้ในทางปรัชญาที่จะกล่าวถึงลักษณะดังกล่าว


1
ฉันรู้ว่านี่เป็นวิธีหลังจากข้อเท็จจริง แต่คุณควรรวม "AndAlso dstProperty.CanWrite" ไว้ในการทดสอบ "If dstProperty IsNot Nothing" เพื่อให้แน่ใจว่าไม่ใช่คุณสมบัติแบบอ่านอย่างเดียว
JamesMLV

@JamesMLV - ขอบคุณที่จับได้ 'after the fact' - ดูเหมือนว่า OP จะไม่ยอมรับคำตอบใด ๆ อยู่แล้ว :-( ดังนั้นจึงไม่มีข้อเท็จจริงที่จะเกิดขึ้นหลังจากนั้นโอ้ดี
FastAl

4

ควรสร้างอินสแตนซ์ของอ็อบเจ็กต์โดยใช้ประเภทของคลาสย่อยคุณไม่สามารถส่งอินสแตนซ์ประเภทพาเรนต์เป็นประเภทลูกได้


2

สำหรับฉันมันก็เพียงพอแล้วที่จะคัดลอกฟิลด์คุณสมบัติทั้งหมดจากคลาสฐานไปยังพาเรนต์ดังนี้:

using System.Reflection;

public static ChildClass Clone(BaseClass b)
{
    ChildClass p = new ChildClass(...);

    // Getting properties of base class

    PropertyInfo[] properties = typeof(BaseClass).GetProperties();

    // Copy all properties to parent class

    foreach (PropertyInfo pi in properties)
    {
        if (pi.CanWrite)
            pi.SetValue(p, pi.GetValue(b, null), null);
    }

    return p;
}

คุณสามารถดูโซลูชันสากลสำหรับวัตถุใด ๆ ได้ที่นี่


2

ใน C # 7.0 คุณสามารถใช้คำหลัก isเพื่อทำสิ่งนี้:

ด้วยคลาสเหล่านั้นที่กำหนดไว้:

class Base { /* Define base class */ }
class Derived : Base { /* Define derived class */ }

จากนั้นคุณสามารถทำบางสิ่งได้เช่น:

void Funtion(Base b)
{
    if (b is Derived d)
    {
        /* Do something with d which is now a variable of type Derived */
    }
}

ซึ่งจะเทียบเท่ากับ:

void Funtion(Base b)
{
    Defined d;
    if (b is Derived)
    {
        d = (Defined)b;
        /* Do something with d */
    }
}

ตอนนี้คุณสามารถโทร:

Function(new Derived()); // Will execute code defined in if

เช่นเดียวกับ

Function(new Base()); // Won't execute code defined in if

ด้วยวิธีนี้คุณจะมั่นใจได้ว่าดาวน์แคสต์ของคุณถูกต้องและจะไม่เกิดข้อยกเว้น!


1

ในการร่ายวัตถุที่แท้จริงจะต้องเป็นประเภทที่เท่ากับหรือได้มา จากประเภทที่คุณพยายามที่จะโยนไป ...

หรือหากต้องการระบุในทางตรงกันข้ามประเภทที่คุณพยายามจะร่ายจะต้องเหมือนกับหรือคลาสพื้นฐานของประเภทที่แท้จริงของวัตถุ

หากวัตถุจริงของคุณเป็นประเภทBaseclassคุณจะไม่สามารถส่งไปยังคลาสที่ได้รับประเภท ...


1

รูปแบบของวิธีการทำให้เป็นอนุกรมสำหรับผู้ที่ใช้ ServiceStack:

var child = baseObject.ConvertTo<ChildType>();

หรือรายละเอียดเพิ่มเติม:

var child = baseObject.ToJson().FromJson<ChildType>();

การทำให้เป็นอนุกรมของ ServiceStack อาจเร็วมากและทั้งหมดนี้ไม่ใช่วิธีแก้ปัญหาสำหรับการแปลงจำนวนมากในการถ่ายโอนเวลาแฝงต่ำหรือสำหรับประเภทที่มีความซับซ้อนสูง น่าจะชัดเจนสำหรับทุกคนที่ใช้ ServiceStack แต่คิดว่าฉันจะชี้แจงเพื่อคาดหวังความคิดเห็น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.