Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternative to calling a virtual method in C#

I am using NHibernate for my C# pojects and therefore I have several model classes.

Lets assume the following example:

using System;

namespace TestProject.Model
{
    public class Room
    {
        public virtual int Id { get; set; }
        public virtual string UniqueID { get; set; }
        public virtual int RoomID { get; set; }
        public virtual float Area { get; set; }

    }
}

Mapping these objects with NHibernate works fine so far. Now I want to generate a new Room object and I want to store it in the database. To avoid setting each member seperatly, I add a new constructor to the model class. Below the virtual members I write:

public RoomProperty()
{

}


public RoomProperty(int pRoomId, int pArea)
{
        UniqueID = Guid.NewGuid().ToString();
        RoomID = pRoomId;
        Area = pArea;
}

Analyzing my code with FxCop tells me the following:

"ConstructorShouldNotCallVirtualMethodsRule"
This rule warns the developer if any virtual methods are called in the constructor of a non-sealed type. The problem is that if a derived class overrides the method then that method will be called before the derived constructor has had a chance to run. This makes the code quite fragile. 

This page also describes why this is wrong and I also understand it. But I am not shure how to solve the problem.

When I erase all constructors and add the following method...

public void SetRoomPropertyData(int pRoomId, int pArea)
        {
            UniqueID = Guid.NewGuid().ToString();
            RoomID = pRoomId;
            Area = pArea;

        }

.... to set the data after I called the standard constructor I cant start my aplication becaue NHibernate fails initializing. It says:

NHibernate.InvalidProxyTypeException: The following types may not be used as proxies:
VITRIcadHelper.Model.RoomProperty: method SetRoomPropertyData should be 'public/protected virtual' or 'protected internal virtual'

But setting this method to virtual would be the same mistake as when I just set the virtual members in the constructor. How can I avoid these mistakes (violations)?

like image 286
Metalhead89 Avatar asked Feb 20 '13 14:02

Metalhead89


2 Answers

The problem lies in virtual set. Passing a value to the virtual property in the base class constructor will use overriden set instead base set. If overriden set relies on data in derived class, then you are in trouble, because constructor of derived class was not done yet.

If you are absolutely sure, that any subclass will not use any data of its state in overriden set, then you can initialize virtual properties in base class constructor. Consider adding an appropriate warning to the documentation.

If possible, try to create backing fields for each property and use them in base class contructor.

You can also postpone properties initialization to the derived class. To achieve that, create an initializing method in the base class that you invoke in constructor of derived class.

like image 96
Ryszard Dżegan Avatar answered Oct 16 '22 08:10

Ryszard Dżegan


I expect one of the following to work:

  1. Make the properties non-virtual (preferred as long as NHibernate supports it).
  2. Change from auto-implemented properties to properties with an explicit backing field, and set the fields in the constructor instead of setting the properties.
  3. Create a static Create method which constructs the object first, and then sets values to the properties before returning the constructed object.

Edit: From the comment I see option #3 was not clear.

public class Room
{
    public virtual int Id { get; set; }
    public virtual string UniqueID { get; set; }
    public virtual int RoomID { get; set; }
    public virtual float Area { get; set; }

    public static Room Create(int roomId, int area)
    {
        Room room = new Room();
        room.UniqueID = Guid.NewGuid().ToString();
        room.RoomID = roomId;
        room.Area = area;
        return room;
    }
}
like image 37
Sam Harwell Avatar answered Oct 16 '22 06:10

Sam Harwell