I'm working on a problem at the moment and I've run into an issue where I have multiple architectural options, but am not sure which is going to be the best option going forward.
Context:
I am writing some code for a game, which uses a tile map. Tiles have common attributes, for example, all floor tiles are walkable, while walls are not (along with other properties). Therefore, it makes sense to have some kind of reference to which each tile can point at a common reference to discern what its properties are.
I have come up with a few solutions, however am uncertain as to which is the most efficient or will provide the greatest flexibility moving forward. Therefore, I am curious as to which would be considered 'best', either in general or for my specific case. Similarly, if there is a better way of doing this that I haven't listed, please let me know.
(As an aside, as the number of tile types grow, I may also run into the issue where it may not be practical to hard code these values and some kind of serialization or file I/O might make more sense. As I have done neither in C#, if you see any potential stumbling blocks here, it would be similarly appreciated if you could include them in your answer.)
Below are each of my three approaches, which I have simplified slightly to make them more general:
Approach #1: Enum with Extension Methods:
public enum TileData{
WALL,
FLOOR,
FARMLAND
//...etc
}
public static class TileDataExtensions{
public static int IsWalkable(this TileData tile){
switch(tile){
case TileData.FLOOR:
case TileData.FARMLAND:
return true;
case TileData.WALL:
return false;
}
}
public static int IsBuildable(this TileData tile){
switch(tile){
case TileData.FLOOR:
return true;
case TileData.WALL:
case TileData.FARMLAND:
return false;
}
}
public static Zone ZoneType(this TileData tile){
switch(tile){
case TileData.WALL:
case TileData.FLOOR:
return Zone.None;
case TileData.FARMLAND:
return Zone.Arable;
}
}
public static int TileGraphicIndex(this TileData tile){
switch(tile){
case TileData.WALL:
return 0;
case TileData.FLOOR:
return 1;
case TileData.FARMLAND:
return 2;
}
}
public enum Zone{
Shipping,
Receiving,
Arable,
None
}
}
Approach #2: Huge Private Constructor & Static Instances
public class TileData{
public bool IsWalkable{get;};
public bool IsBuildSpace{get;};
public Zone ZoneType{get;};
public int TileGraphicIndex{get;};
public static TileData FLOOR = new TileData(true, true, Zone.None, 1);
public static TileData WALL = new TileData(false, false, Zone.None, 0);
public static TileData FARMLAND = new TileData(true, false, Zone.Arable, 2);
//...etc
private TileData(bool walkable, bool buildSpace, Zone zone, int grahpicIndex){
IsWalkable = walkable;
IsBuildSpace = buildSpace;
ZoneType = zone;
TileGraphicIndex = grahpicIndex;
}
public enum Zone{
Shipping,
Receiving,
Arable,
None
}
}
Approach #3: Private Constructor and Setters, with Static Instances:
public class TileData{
public bool IsWalkable{get; private set;};
public bool IsBuildSpace{get; private set;};
public Zone ZoneType{get; private set;};
public int TileGraphicIndex{get; private set;};
public static TileData FLOOR{
get{
TileData t = new TileData();
t.IsBuildSpace = true;
t.TileGraphicIndex = 1;
return t;
}
}
public static TileData WALL{
get{
TileData t = new TileData();
t.IsWalkable = false;
return t;
}
}
public static TileData FARMLAND{
get{
TileData t = new TileData();
t.ZoneType = Zone.Arable;
t.TileGraphicIndex = 2;
return t;
}
}
//...etc
//Constructor applies the most common values
private TileData(){
IsWalkable = true;
IsBuildSpace = false;
ZoneType = Zone.None;
TileGraphicIndex = 0;
}
public enum Zone{
Shipping,
Receiving,
Arable,
None
}
}
Many thanks, LR92
EDIT: The types of tiles are determined before compile time by the designer, ie no class should be allowed to create new TileData types (ie, in examples 2&3, instances).
Approach 2 is friendly to the designer and is slightly more efficient than Approach 3. It can also be supplemented by Approach 1's extension methods if you want to do some reasoning system-by-system instead of tile-by-tile.
Consider supplementing your constructor with a static factory:
private TileData(bool walkable, bool buildSpace, Zone zone, int grahpicIndex){
IsWalkable = walkable;
IsBuildSpace = buildSpace;
ZoneType = zone;
TileGraphicIndex = grahpicIndex;
}
private static TileData Tweak(TileData parent, Action<TileData> tweaks) {
var newTile = parent.MemberwiseClone();
tweaks(newTile);
return newTile;
}
This allows you to build your tile types with a sort of prototypal inheritance (except instead of looking up the chain of prototypes at runtime, it will be baked in). This should be very useful as it is common in tile-based games to have tiles that are mostly similar but have slightly different behaviors or graphics.
public readonly static TileData GRASS = new TileData(etc.);
public readonly static TileData WAVY_GRASS = Tweak(GRASS, g => g.TileGraphicIndex = 10);
public readonly static TileData JERKFACE_GRASS = Tweak(GRASS, g => g.IsWalkable = false);
public readonly static TileData SWAMP_GRASS = Tweak(GRASS, g => {g.TileGraphicIndex = 11; g.IsBuildable = false;});
Note: when you serialize/deserialize your tile maps, you'll want to have a consistent ID of some sort assigned to each tile (in particular, this makes working with Tiled easier). You could pass that in to the constructor (and to Tweak, as another argument, because otherwise the tweaked tile will have cloned the ID of its parent!). It would be good then to have something (a unit test would be fine) that ensures that all fields of this class of type TileData have distinct IDs. Finally, to avoid having to re-enter these IDs into Tiled, you could make something that exports the data from this class into a Tiled TSX or TMX file (or similar file for whatever map editor you ultimately go with).
EDIT: One last tip. If your consistent IDs are consecutive ints, you can "compile" your tile data into static arrays split out by property. This can be useful for systems in which performance is important (for example, pathfinding will need to look up walkability a lot).
public static TileData[] ById = typeof(TileData)
.GetFields(BindingFlags.Static | BindingFlags.Public)
.Where(f => f.FieldType == typeof(TileData))
.Select(f => f.GetValue(null))
.Cast<TileData>()
.OrderBy(td => td.Id)
.ToArray();
public static bool[] Walkable = ById.Select(td => td.IsWalkable).ToArray();
// now you can have your map just be an array of array of ids
// and say things like: if(TileData.Walkable[map[y][x]]) {etc.}
If your ids are not consecutive ints, you can use Dictionary<MyIdType, MyPropertyType> for the same purpose and access it with the same syntax, but it wouldn't perform as well.
Let's try to solve your requirement with more object oriented approach. Less conditional more polymorphism. In my opinion if you have more chances to come up with new Types of Tiles apart from the mentioned ones. Means the design should be extensible and should be open for minimal change to introduce new component.
For e.g. Let's keep the Tile class a base class.
public abstract class Tile
{
public Tile()
{
// Default attributes of a Tile
IsWalkable = false;
IsBuildSpace = false;
ZoneType = Zone.None;
GraphicIndex = -1;
}
public virtual bool IsWalkable { get; private set; }
public virtual bool IsBuildSpace { get; private set; }
public virtual Zone ZoneType { get; private set; }
public virtual int GraphicIndex { get; private set; }
/// <summary>
/// Factory to build the derived types objects
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T Get<T>() where T : Tile, new()
{
return new T();
}
}
Now we have defined a Tile with default attributes. If required more default attributes of a Tile can be added as Vitual properties. Since this class is abstract one can not simply create the object so a Derived class has to be introduced which would be our specific type of Tile e.g. Wall, Floor etc.
public class Floor : Tile
{
public override bool IsBuildSpace
{
get { return true; }
}
public override bool IsWalkable
{
get { return true; }
}
public override int GraphicIndex
{
get { return 1; }
}
}
public class Wall : Tile
{
public override int GraphicIndex
{
get { return 0; }
}
public override Zone ZoneType
{
get { return Zone.Arable; }
}
}
If a new type of tile has to be created. Just inherit the class from Tile and Override the properties which would require to have specific values instead of defaults.
Crafting a tile would be done via base class just by invoking the generic static factory method Get<>() that will only accept a derived type of Tile:
Tile wallLeft = Tile.Get<Wall>();
Tile floor = Tile.Get<Floor>();
So Everything is Tile and represents a different set of values of defined properties. They can be identified Either by their type or values of properties. And more importantly as you can see we got rid of all the If..Else, Switch case, Constructor overloads. Hows that sound?
Extending the Tile with new attributes
So for e.g. a new property/attribute is required on Tiles e.g. Color simple add a Virtual property to Tile class named Color. In constructor give it a default value. Optinally (not mandatory) Override the property in child classes if your tile should be in special color.
Introducing new Type of Tile
Simply derive the New Tile type with Tile class and override required properties.
Why not just overload the constructor?
public class TileData{
public bool IsWalkable{get;};
public bool IsBuildSpace{get;};
public Zone ZoneType{get;};
public int TileGraphicIndex{get;};
public static TileData FLOOR = new TileData(true, true, Zone.None, 1);
public static TileData WALL = new TileData(false, false, Zone.None, 0);
public static TileData FARMLAND = new TileData(true, false, Zone.Arable, 2);
//...etc
public TileData(bool walkable, bool buildSpace, Zone zone, int grahpicIndex){
IsWalkable = walkable;
IsBuildSpace = buildSpace;
ZoneType = zone;
TileGraphicIndex = grahpicIndex;
}
public TileData(){
IsWalkable = true;
IsBuildSpace = false;
ZoneType = Zone.None;
TileGraphicIndex = 0;
}
public enum Zone{
Shipping,
Receiving,
Arable,
None
}
}
What about methods to create each type of tile.
public class Tile{
public TileType Type { get; private set; }
public bool IsWalkable { get; private set; }
public bool IsBuildSpace { get; private set; }
public Zone ZoneType { get; private set; }
public int TileGraphicIndex { get; private set; }
private Tile() {
}
public static Tile BuildTile(TileType type){
switch (type) {
case TileType.WALL:
return BuildWallTile();
case TileType.FLOOR:
return BuildFloorTile();
case TileType.FARMLAND:
return BuildFarmlandTile();
default:
throw ArgumentException("type");
}
}
public static Tile BuildWallTile()
{
return new Tile {
IsWalkable = false,
IsBuildSpace = false,
ZoneType = Zone.None,
TileGraphicIndex = 1,
Type = TileType.WALL
};
}
public static Tile BuildFloorTile()
{
return new Tile {
IsWalkable = true,
IsBuildSpace = None,
ZoneType = Zone.None,
TileGraphicIndex = 1,
Type = TileType.FLOOR
};
}
public static Tile BuildFarmlandTile()
{
return new Tile {
IsWalkable = true,
IsBuildSpace = false,
ZoneType = Zone.Arable,
TileGraphicIndex = 2,
Type = TileType.FARMLAND
};
}
public enum Zone{
Shipping,
Receiving,
Arable,
None
}
public enum TileType{
WALL,
FLOOR,
FARMLAND
//...etc
}
}
Just extending on Diegos answer, the methods can just be fields for cleanliness
public class Tile{
public TileType Type { get; private set; }
public bool IsWalkable { get; private set; }
public bool IsBuildSpace { get; private set; }
public Zone ZoneType { get; private set; }
public int TileGraphicIndex { get; private set; }
private Tile() { }
public static Tile BuildTile(TileType type){
switch (type) {
case TileType.WALL: return BuildWallTile();
case TileType.FLOOR: return BuildFloorTile();
case TileType.FARMLAND: return BuildFarmlandTile();
default: throw ArgumentException("type");
}
}
public static Tile wall {
get {
return new Tile {
IsWalkable = false,
IsBuildSpace = false,
ZoneType = Zone.None,
TileGraphicIndex = 1,
Type = TileType.WALL
};
}
}
public static Tile floor {
get {
return new Tile {
IsWalkable = true,
IsBuildSpace = None,
ZoneType = Zone.None,
TileGraphicIndex = 1,
Type = TileType.FLOOR
};
}
}
public static Tile farmland {
get {
return new Tile {
IsWalkable = true,
IsBuildSpace = false,
ZoneType = Zone.Arable,
TileGraphicIndex = 2,
Type = TileType.FARMLAND
};
}
}
public enum Zone{
Shipping,
Receiving,
Arable,
None
}
public enum TileType{ WALL, FLOOR, FARMLAND //...etc }
}
Usage:
Tile myWallTile = Tile.wall;
Tile myFloorTile = Tile.floor;
I wanted to present a totally different (and self admittedly insane) approach from the many suggestions thus far. If you're willing to totally throw type safety out the window, consider this:
public interface IValueHolder
{
object Value {get; set;}
}
public class IsWalkable : Attribute, IValueHolder
{
public object Value {get; set;}
public IsWalkable(bool value)
{
Value = value;
}
}
public class IsBuildSpace : Attribute, IValueHolder
{
public object Value {get; set;}
public IsBuildSpace(bool value)
{
Value = value;
}
}
public enum Zone
{
None,
Arable,
}
public class ZoneType : Attribute, IValueHolder
{
public object Value {get; set;}
public ZoneType(Zone value)
{
Value = value;
}
}
public class TileGraphicIndex : Attribute, IValueHolder
{
public object Value {get; set;}
public TileGraphicIndex(int value)
{
Value = value;
}
}
public class TileAttributeCollector
{
protected readonly Dictionary<string, object> _attrs;
public object this[string key]
{
get
{
if (_attrs.ContainsKey(key)) return _attrs[key];
else return null;
}
set
{
if (_attrs.ContainsKey(key)) _attrs[key] = value;
else _attrs.Add(key, value);
}
}
public TileAttributeCollector()
{
_attrs = new Dictionary<string, object>();
Attribute[] attrs = Attribute.GetCustomAttributes(this.GetType());
foreach (Attribute attr in attrs)
{
IValueHolder vAttr = attr as IValueHolder;
if (vAttr != null)
{
this[vAttr.ToString()]= vAttr.Value;
}
}
}
}
[IsWalkable(true), IsBuildSpace(false), ZoneType(Zone.Arable), TileGraphicIndex(2)]
public class FarmTile : TileAttributeCollector
{
}
An example of usage:
FarmTile tile = new FarmTile();
// read, can be null.
var isWalkable = tile["IsWalkable"];
// write
tile["IsWalkable"] = false;
// add at runtime.
tile["Mom"]= "Ingrid Carlson of Norway";
Related
My brain is gonna to explode. :) So I would like to get help from you.
Please, think about my question like about just programmer puzzle. (Actually. perhaps it is very easy question for you, but not for me.)
It is needed to create array of objects. For example List where T is class. (I will describe Class T below). Also it is needed create “container” that will contain this array and some methods for work with this array. For example Add(), Remove(int IndexToRemove).
Class T must have field "Container", this way each elements of our array would be able to know where is it contained and has access its container's fields and methods. Notice, that in this case Class T should have type parameter. Indeed, it is not known beforehand which container's type is used.
Let us denote this class container as A and class element (class T) as AUnit.
Code:
class Program
{
static void Main(string[] args)
{
A a = new A();
a.Add();
a.Units[0].SomeField +=100;
Console.ReadKey();
}
}
class A
{
public List<AUnit> Units;
public A()//ctor
{
Units = new List<AUnit>();
}
public void Add()
{
this.Units.Add(new AUnit(this));
}
}
class AUnit
{
public int SomeField;
public A Container;
public string Name { get; private set; }
public AUnit(A container)
{
this.SomeField = 43;
this.Container = container;
this.Name = "Default";
}
}
Public fields should be protected or private of course, but let think about this later.
You can ask “why we create public A Container field in AUnit”? We create field public string Name{get;private set;} (actually property but nevermind). And also we would like to be able to change value of this field for example method [Class AUnit] public bool Rename(string newName)();. The main idea of this method is changing Name field only that case if no one element in array (public List Units; ) has the same name like newName. But to achieve this, Rename method has to have access to all names that is currently used. And that is why we need Container field.
Code of extended version AUnit
class AUnit
{
public int SomeField;
public A Container;
public string Name { get; private set; }
public AUnit(A container)
{
this.SomeField = 43;
this.Container = container;
this.Name = "Default";
}
public bool Rename(String newName)
{
Boolean res = true;
foreach (AUnit unt in this.Container.Units)
{
if (unt.Name == newName)
{
res = false;
break;
}
}
if (res) this.Name = String.Copy(newName);
return res;
}
}
Ok. If you still read it let's continue. Now we need to create Class B and class BUnit which will be very similar like Class A and Class Aunit. And finally the main question of this puzzle is HOW WE CAN DO IT? Of course, I can CopyPaste and bit modify A and AUnit and create this code.
class B
{
public List<BUnit> Units; //Only Type Changing
public B()//ctor Name changing...
{
Units = new List<BUnit>();//Only Type Changing
}
public void Add()
{
this.Units.Add(new BUnit(this));//Only Type Changing
}
}
class BUnit
{
public int SomeField;
public B Container;//Only Type Changing
public string Name { get; private set; }
public A a; //NEW FIELD IS ADDED (just one)
public BUnit(B container) //Ctor Name and arguments type changing
{
this.SomeField = 43;
this.Container = container;
this.Name = "Default";
this.a=new A(); //New ROW (just one)
}
public bool Rename(String newName)
{
Boolean res = true;
foreach (BUnit unt in this.Container.Units) //Only Type Changing
{
if (unt.Name == newName)
{
res = false;
break;
}
}
if (res) this.Name = String.Copy(newName);
return res;
}
}
And I can to use this classes this way.
static void Main(string[] args)
{
B b = new B();
b.Add();
b.Units[0].a.Add();
b.Units[0].a.Units[0].SomeField += 100;
bool res= b.Units[0].a.Units[0].Rename("1");
res = b.Units[0].a.Units[0].Rename("1");
Console.ReadKey();
}
This construction is can be used to create “non-homogeneous trees”.
Help, I need somebody help, just no anybody…. [The Beatles]
I created B and BUnit using CopyPaste.
But how it can be done using “macro-definitions” or “Generic”, inherit or anything else in elegant style? (C# language)
I think that there is no reason to describe all my unsuccessful attempts and subquestions. Already topic is too long. : )
Thanks a lot if you still read it and understand what I would like to ask.
You need to implement a base type, lets call it UnitBase, with all common functionality. I'd structure your code the following way:
Create an interface for your container, this way you can change implementation to more performant solutions without modifying the elements you will be adding to the container.
public interface IContainer
{
Q Add<Q>() where Q : UnitBase, new();
IEnumerable<UnitBase> Units { get; }
}
Following the idea stated in 1, why not make the search logic belong to the container? It makes much more sense, as it will mostly depend on how the container is implemented:
public interface IContainer
{
Q Add<Q>() where Q : UnitBase, new();
IEnumerable<UnitBase> Units { get; }
bool Contains(string name);
}
A specific implementation of IContainer could be the following:
public class Container : IContainer
{
public Container()
{
list = new List<UnitBase>();
}
private List<UnitBase> list;
public Q Add<Q>() where Q: UnitBase, new()
{
var newItem = Activator.CreateInstance<Q>();
newItem.SetContainer(this);
list.Add(newItem);
return newItem;
}
public IEnumerable<UnitBase> Units => list.Select(i => i);
public bool Contains(string name) =>
Units.Any(unit => unit.Name == name);
}
Create a base class for your AUnit and BUnit types condensing all common functionality:
public abstract class UnitBase
{
protected UnitBase()
{
}
public IContainer Container { get; private set; }
public int SomeField;
public string Name { get; private set; }
public void SetContainer(IContainer container)
{
Container = container;
}
public bool Rename(String newName)
{
if (Container.Contains(newName))
return false;
this.Name = newName; //No need to use String.Copy
return true;
}
}
Implement your concrete types:
public class BUnit : UnitBase
{
public int SpecificBProperty { get; private set; }
public BUnit()
{
}
}
Shortcomings of this approach? Well, the container must be of type <UnitBase>, I've removed the generic type because it really wasn't doing much in this particular case as it would be invariant in the generic type.
Also, keep in mind that nothing in the type system avoids the following:
myContainer.Add<BUnit>();
myContainer.Add<AUnit>();
If having two different types in the same container is not an option then this whole set up kind of crumbles down. This issue was present in the previous solution too so its not something new, I simply forgot to point it out.
InBetween , I am very thankful to you for your advices. Actually I can't say that I understood your answer in full, but using your ideas I have done what I want.
Looks like my variant works well. However I would like to hear your (and everyone) opinions about code described below. The main goal of this structure is creating non-homogeneous trees. So could you estimate it from this side.
First of all. We need to create interfaces for both classes. We describe there all "cross-used" functions.
public interface IUnit<T>
{
string Name { get;}
void SetContainer(T t);
bool Rename(String newName);
}
public interface IContainer
{
bool IsNameBusy(String newName);
int Count { get; }
}
Next. Create Base for Unit Classes for future inheritance. We will use in this inheritors methods from Container Base so we need generic properties and IUnit interface.
class UnitBase<T> : IUnit<T> where T : IContainer
Unfortunately I don't know yet how to solve the problem with Constructor parameters. That is why I use method
SetContainer(T container).
Code:UnitBase
class UnitBase<T> : IUnit<T> where T : IContainer
{
protected T Container;
public string Name { get; private set; }
public UnitBase()
{
this.Name = "Default";
}
public void SetContainer(T container)
{
this.Container = container;
}
public bool Rename(String newName)
{
bool res = Container.IsNameBusy(newName);
if (!res) this.Name = String.Copy(newName);
return !res;
}
}
Next. Create ContainerBase
ContainerBase should:
1) has IContainer interface.
2)has information about what it will contain:
... where U : IUnit<C>, new()
3)and .... has information about what itself is. This information we need to pass as parameter to SetContainer() method.
Code ContainerBase:
class ContainerBase<U, C> : IContainer //U - Unit Class. C-Container Class
where U : IUnit<C>, new()
where C : ContainerBase<U, C>
{
protected List<U> Units;
public U this[int index] { get { return Units[index]; } }
public ContainerBase()//ctor
{
this.Units = new List<U>();
}
public void Add()
{
this.Units.Add(new U());
this.Units.Last().SetContainer(((C)this));//may be a bit strange but actualy this will have the same type as <C>
}
public bool IsNameBusy(String newName)
{
bool res = false;
foreach (var unt in this.Units)
{
if (unt.Name == newName)
{
res = true;
break;
}
}
return res;
}
public int Count { get { return this.Units.Count; } }
}
Cast ((TContainer)(this)) may be is a bit strange. But using ContainerBase we always should use NewInheritorContainer. So this cast is just do nothing…looks like...
Finally. This classes can be used like in this example.
class SheetContainer : ContainerBase<SheetUnit,SheetContainer> {public SheetContainer(){}}
class SheetUnit : UnitBase<SheetContainer>
{
public CellContainer Cells;
public PictureContainer Pictures;
public SheetUnit()
{
this.Cells = new CellContainer();
this.Pictures = new PictureContainer();
}
}
class CellContainer : ContainerBase<CellUnit, CellContainer> { public CellContainer() { } }
class CellUnit : UnitBase<CellContainer>
{
public string ValuePr;//Private Field
private const string ValuePrDefault = "Default";
public string Value//Property for Value
{
//All below are Just For Example.
get
{
return this.ValuePr;
}
set
{
if (String.IsNullOrEmpty(value))
{
this.ValuePr = ValuePrDefault;
}
else
{
this.ValuePr = String.Copy(value);
}
}
}
public CellUnit()
{
this.ValuePr = ValuePrDefault;
}
}
class PictureContainer : ContainerBase<PictureUnit, PictureContainer> { public PictureContainer() { } }
class PictureUnit : UnitBase<PictureContainer>
{
public int[,] Pixels{get;private set;}
public PictureUnit()
{
this.Pixels=new int[,]{{10,20,30},{11,12,13}};
}
public int GetSizeX()
{
return this.Pixels.GetLength(1);
}
public int GetSizeY()
{
return this.Pixels.GetLength(0);
}
public bool LoadFromFile(string path)
{
return false;
}
}
static void Main(string[] args)
{
SheetContainer Sheets = new SheetContainer();
Sheets.Add();
Sheets.Add();
Sheets.Add();
Sheets[0].Pictures.Add();
Sheets[1].Cells.Add();
Sheets[2].Pictures.Add();
Sheets[2].Cells.Add();
Sheets[2].Cells[0].Value = "FirstTest";
bool res= Sheets[0].Rename("First");//res=true
res=Sheets[2].Rename("First");//res =false
int res2 = Sheets.Count;
res2 = Sheets[2].Pictures[0].Pixels[1, 2];//13
res2 = Sheets[2].Pictures.Count;//1
res2 = Sheets[1].Pictures.Count;//0
res2 = Sheets[0].Pictures[0].GetSizeX();//3
Console.ReadKey();
}
Looks like it works like I want. But I didn’t test it full.
Let me say Thank you again, InBetween.
in numerous other Types I have created it is possible to downCast a type
and i usually Create An Extension method too so it will be easier to manage...
BaseTypeM
BTDerV : BaseTypeM
BTDerLastDescndnt : BTDerV
now i create A LastDerived Type and assign its value To ParentType
BTDerV BTDer;
BTDerLastDescndnt BTDerLastDesc = new BTDerLastDescndnt(parA, ParB);
this.BTDer = BTDerLastDesc;
then using the downCast Extension
var LDesc = this.BTDer.AsBTDerLastDescndnt();
which is actually
public static BTDerLastDescndnt AsBTDerLastDescndnt(this BTDerV SelfBTDerV )
{
return (BTDerLastDescndnt)SelfBTDerV;
}
now when i do this as the code below, here it does compile but gives me a run-time error
//BTDerV---v v---BaseTypeM
public class SqlParDefV : SqlParameterM
{
public override SqlSpParDefMeta ParDMT
{
get {
return base.ParDMT;
}
set {
base.ParDMT = value;
}
}
public SqlParDefV(int bsprpOrdinal, string bsprpParName, MSSTypesS bdprpTypeS, bool bsprpIsDbStuctured, bool bsprpIsReq = true, ParameterDirection bsprpDirection = ParameterDirection.Input)
{
this.ParDMT = new SqlSpParDefMeta(bsprpOrdinal, bsprpParName, bdprpTypeS, bsprpIsReq, bsprpIsDbStuctured, bsprpDirection);
}
}
//BTDerLastDescndnt---v
public sealed class SqlParTvDrecDefinitionVScl : SqlParDefV
{
public override SqlSpParDefMeta ParDMT
{
get {
return base.ParDMT;
}
set {
base.ParDMT = value;
}
}
public SprocTvTargetSF.currentSDTObjType SqlObjType { get; set; }
public SqlMetaData[] Meta { get; set; }
public SqlParTvDrecDefinitionVScl(int bsprpOrdinal, string bsprpParName, SprocTvTargetSF.currentSDTObjType ctrSqlObjType, SqlMetaData[] parGeneratedSqlMetaData, MSSTypesS bdprpTypeS, bool bsprpIsDbStuctured, bool bsprpIsReq = true, ParameterDirection bsprpDirection = ParameterDirection.Input)
: base(bsprpOrdinal, bsprpParName, bdprpTypeS, bsprpIsDbStuctured, bsprpIsReq, bsprpDirection)
{
this.SqlObjType = ctrSqlObjType;
this.Meta = parGeneratedSqlMetaData;
}
}
is there something unusual here or am i confused and missed some basic rule ?
I am unsure of the precise reasons a cast from Derived to MoreDerived fails here. However, a potential workaround (note: possibly code smell) is the as operator:
public static MoreDerived AsMoreDerived (this Derived d)
{
return d as MoreDerived;
}
Note that as effectively attempts the cast and returns null, so you'll need an appropriate check there.
I have a few classes which have some primitive fields and I would like to create a generalized wrapper for them in order to access their fields. This wrapper should somehow contain a reference to the fields of my classes so that I can read/write the values of these fields. The idea is to create a genralized architecture for these classes so that I dont have to write code for each of them. The classes have fields which have a number in them which will be used as an Id to access the fields.
This is some example code that might shed some light on my requirement. What I want in the end is to change the value of some field in the object of Fancy1 class without accessing the object itself but through its wrapper.
class Fancy1
{
public double level1;
public bool isEnable1;
public double level2;
public bool isEnable2;
public double level3;
}
class Fancy2
{
public double level4;
public bool isEnable4;
public double level6;
public bool isEnable6;
public double level7;
}
class FieldWrapper
{
public int id { get; set; }
public object level { get; set; }
public object isEnabled { get; set; }
public FieldWrapper(int id, object level, object isEnabled)
{
this.id = id;
this.level = level;
this.isEnabled = isEnabled;
}
}
class FancyWrapper
{
private Fancy scn;
public FancyWrapper(Fancy scn)
{
if (!(scn is Fancy))
throw new ArgumentException(scn.GetType().FullName + " is not a supported type!");
this.scn = scn;
}
private Dictionary<int, FieldWrapper> fieldLut = new Dictionary<int, FieldWrapper>();
private List<FieldWrapper> _fields { get { return fieldLut.Values.ToList(); } }
public List<FieldWrapper> fields
{
get
{
if (_fields.Count == 0)
{
foreach (System.Reflection.FieldInfo fieldInfo in scn.GetType().GetFields())
{
if (fieldInfo.FieldType == typeof(double))
{
int satId = getIdNr(fieldInfo.Name);
fieldLut.Add(satId, new FieldWrapper(satId, fieldInfo.GetValue(scn), true));
}
}
foreach (System.Reflection.FieldInfo fieldInfo in scn.GetType().GetFields())
{
if (fieldInfo.FieldType == typeof(bool))
{
int satId = getIdNr(fieldInfo.Name);
fieldLut[satId].isEnabled = fieldInfo.GetValue(scn);
}
}
}
return _fields;
}
}
private int getIdNr(string name)
{
System.Text.RegularExpressions.Match m = System.Text.RegularExpressions.Regex.Match(name, #"\d+");
return Int32.Parse(m.Value);
}
}
class Program
{
static void Main(string[] args)
{
Fancy1 fancy = new Fancy1();
fancy.level1 = 1;
fancy.isEnable1 = true;
fancy.level2 = 2;
fancy.isEnable2 = false;
fancy.level3 = 3;
FancyWrapper wrapper = new FancyWrapper(fancy);
wrapper.fields[2].level = 10;
// fancy.level2 should somehow get the value I set via the wrapper
Console.WriteLine(fancy.level2);
Console.ReadLine();
}
}
EDIT: Fancy classes cannot be changed since they are part of an interface!
Depending on how many Fancy classes you are dealing with, you could create an adapter/facade class for each the expose a common interface. eg:
class Fancy1
{
public double level1;
public bool isEnable1;
public double level2;
public bool isEnable2;
public double level3;
}
public class FieldWrapper
{
private Action<double> _levelSetter;
private Func<double> _levelGetter;
private Action<bool> _enableSetter;
private Func<bool> _enableGetter;
public double level { get { return _levelGetter(); } set { _levelSetter(value); }}
public bool isEnabled { get { return _enableGetter(); } set { _enableSetter(value); }}
internal FieldWrapper(Func<double> levelGetter, Action<double> levelSetter, Func<bool> enableGetter, Action<bool> enableSetter)
{
_levelGetter = levelGetter;
_levelSetter = levelSetter;
_enableGetter = enableGetter;
_enableSetter = enableSetter;
}
}
abstract class FancyWrapper
{
public FieldWrapper[] Fields { get; protected set; }
}
class Fancy1Wrapper : FancyWrapper
{
private Fancy1 _fancy1;
public Fancy1Wrapper(Fancy1 fancy1)
{
_fancy1 = fancy1;
this.Fields = new[] { new FieldWrapper(() => fancy1.level1, level => _fancy1.level1 = level, () => _fancy1.isEnable1, enable => _fancy1.isEnable1 = enable),
new FieldWrapper(() => fancy1.level2, level => _fancy1.level2 = level, () => _fancy1.isEnable2, enable => _fancy1.isEnable2 = enable), };
}
}
Or you could invest 5 minutes learning data structures. Consider following example:
var levels = new Dictionary<int, bool>
{
{1, true},
{2, false}
};
if (levels[1])
{
//will run, because level 1 is true
}
if (levels[2])
{
//will not run, because level 2 is false
}
if (levels.ContainsKey(3) && levels[3])
{
//will not run, because dictionary does not contain entry for key 3
}
levels.Add(3, false);
if (levels.ContainsKey(3) && levels[3])
{
//will not run, because level 3 is false
}
levels[3] = true;
if (levels.ContainsKey(3) && levels[3])
{
//will run, because level 3 is true
}
That may seem like what you want, but it really isn't. It is extremely awkward on any number of levels. More specifically, pointers are generally rather "Un-C#-like" and having to know about these numbers defeats the point of having separate classes to begin with.
Think closely about what you want to accomplish. If you're having problems translating it into code, we're here to help. :)
I need to have possibility of comparison of Product, AdvancedProduct (and other classes that inherit from class Product)
How it is better to realize hierarchical check of fields? For example, i want to check two AdvancedProduct classes, first i check fields the basic class Product then check additional fields of AdvancedProduct and return in some form (???) changes between them (maybe class PChanges???). Whether there is a suitable template?
How make all this, but to make rather flexibly for the subsequent use?
public class Product
{
public string ID;
public string Name;
public Product(string id, string name)
{
this.ID = id;
this.Name = name;
}
}
public class AdvancedProduct : Product
{
public string CurrentVersion;
public AdvancedProduct(string id, string name, string version)
: base(id, name) { }
}
public class PChanges
{
public bool NameChanged = false;
public bool VersionChanged = false;
public PChanges() { }
}
public class ProductComparer
{
PChanges changes = new PChanges();
public ProductComparer() { }
public PChanges Compare(AdvancedProduct p1, AdvancedProduct p2)
{
if (p1.Name != p2.Name)
changes.NameChanged = true;
if (p1.CurrentVersion != p2.CurrentVersion)
changes.VersionChanged = true;
return changes;
}
}
There is a nice library for .NET called Compare .NET Objects. It can be used to compare complex objects without writing comparison code. It is also quite customizable - you can tell it to exclude certain properties, include others, etc. It can compare both flat objects and object hierarchies. You can download it from CodePlex - http://comparenetobjects.codeplex.com/.
Based on Uzzy's answer, looks like it can be extended to track the change. It is bad practice, yes, but for small app it should be enough. Example:
public class ProductComparer : IEqualityComparer<Product>{
private PChanges change;
public PChanges Changes{ get { return change; } }
public bool Equals(Product p1, Product p2){
PChanges newChange = new PChanges();
bool equal = true;
if(p1.Name != p2.Name){
newChange.NameChange = true;
equal = false;
}
this.change = newChange;
return equal;
}
}
EDIT:
I misread the requirement of extendable field comparison. If that is the case, then Decorator pattern is the best for you. Assuming that every other Product class should be inherited from Product class.
public class ProductComparer{
public virtual void TrackChange(Product p1, Product p2, ref PChange change){
if(p1.Name != p2.Name){
change.NameChange = true;
}
// other base validation
}
}
public class AdvancedProductComparer : ProductComparer{
public AdvancedProductComparer(ProductComparer baseComparer){
this.baseComparer = baseComparer;
}
ProductComparer baseComparer;
public override void TrackChange(Product p1, Product p2, ref PChange change){
baseComparer.Compare(p1, p2, ref change);
if( ((AdvancedProduct)p1).CurrentVersion != ((AdvancedProduct)p2).CurrentVersion){
change.CurrentVersion = true;
}
}
}
public class ProductComparerService{
public ProductComparerService(ProductComparer comparer){
this.comparer = comparer;
}
ProductComparer comparer;
private PChanges change;
public PChanges Changes{ get { return change; } }
public bool Equals(Product p1, Product p2){
PChanges newChange = new PChanges();
comparer.Compare(p1,p2, ref newChange);
this.change = newChange;
return (newChange.CurrentVersion || newChange.NameChange);
}
}
The usage:
ProductComparer pCompare = new ProductComparer();
AdvancedProductComparer advCompare = new AdvancedProductComparer(pCompare);
ProductComparerService service = new ProductComparerService(advCompare);
if( service.Equals(p1,p2) ){
PChange change = service.Change;
}
It is better when ProductComparer would implement IEqulaityComparer
For more details see the example in given link.
I've created a class with properties that have default values. At some point in the object's lifetime, I'd like to "reset" the object's properties back to what they were when the object was instantiated. For example, let's say this was the class:
public class Truck {
public string Name = "Super Truck";
public int Tires = 4;
public Truck() { }
public void ResetTruck() {
// Do something here to "reset" the object
}
}
Then at some point, after the Name and Tires properties have been changed, the ResetTruck() method could be called and the properties would be reset back to "Super Truck" and 4, respectively.
What's the best way to reset the properties back to their initial hard-coded defaults?
You can have the initialization in a method instead of inlining with the declaration. Then have the constructor and reset method call the initialization method:
public class Truck {
public string Name;
public int Tires;
public Truck() {
Init();
}
public void ResetTruck() {
Init();
}
private void Init() {
Name = "Super Truck";
Tires = 4;
}
}
Another way is not to have a reset method at all. Just create a new instance.
Reflection is your friend. You could create a helper method to use Activator.CreateInstance() to set the default value of Value types and 'null' for reference types, but why bother when setting null on a PropertyInfo's SetValue will do the same.
Type type = this.GetType();
PropertyInfo[] properties = type.GetProperties();
for (int i = 0; i < properties.Length; ++i)
properties[i].SetValue(this, null); //trick that actually defaults value types too.
To extend this for your purpose, have private members:
//key - property name, value - what you want to assign
Dictionary<string, object> _propertyValues= new Dictionary<string, object>();
List<string> _ignorePropertiesToReset = new List<string>(){"foo", "bar"};
Set the values in your constructor:
public Truck() {
PropertyInfo[] properties = type.GetProperties();
//exclude properties you don't want to reset, put the rest in the dictionary
for (int i = 0; i < properties.Length; ++i){
if (!_ignorePropertiesToReset.Contains(properties[i].Name))
_propertyValues.Add(properties[i].Name, properties[i].GetValue(this));
}
}
Reset them later:
public void Reset() {
PropertyInfo[] properties = type.GetProperties();
for (int i = 0; i < properties.Length; ++i){
//if dictionary has property name, use it to set the property
properties[i].SetValue(this, _propertyValues.ContainsKey(properties[i].Name) ? _propertyValues[properties[i].Name] : null);
}
}
Unless creating the object is really expensive (and Reset isn't for some reason). I see no reason to implement a special reset method. Why don't you just create a new instance with a usable default state.
What is the purpose of reusing the instance?
If you did your initialization in a Reset method you can be good to go:
public class Truck {
public string Name;
public int Tires;
public Truck() {
ResetTruck();
}
public void ResetTruck() {
Name = "Super Truck";
Tires = 4;
}
}
Focusing of separation of concerns (like Brian mentioned in the comments), another alternative would be to add a TruckProperties type (you could even add your default values to its constructor):
public class TruckProperties
{
public string Name
{
get;
set;
}
public int Tires
{
get;
set;
}
public TruckProperties()
{
this.Name = "Super Truck";
this.Tires = 4;
}
public TruckProperties(string name, int tires)
{
this.Name = name;
this.Tires = tires;
}
}
Inside your Truck class, all you would do is manage an instance of the TruckProperties type, and let it do its reset.
public class Truck
{
private TruckProperties properties = new TruckProperties();
public Truck()
{
}
public string Name
{
get
{
return this.properties.Name;
}
set
{
this.properties.Name = value;
}
}
public int Tires
{
get
{
return this.properties.Tires;
}
set
{
this.properties.Tires = value;
}
}
public void ResetTruck()
{
this.properties = new TruckProperties();
}
}
This certainly may be a lot of (unwanted) overhead for such a simple class, but in a bigger/more complex project it could be advantageous.
That's the thing about "best" practices... a lot of times, there's no silver bullet, but only recommendations you must take with skepticism and your best judgement as to what applies to you in a particular case.
I solved a similar problem with reflection. You can use source.GetType().GetProperties() to get a list of all properties which belong to the object.
Although, this is not always a complete solution. If your object implements several interfaces, you will also get all those properties with your reflection call.
So I wrote this simple function which gives us more control of which properties we are interested in resetting.
public static void ClearProperties(object source, List<Type> InterfaceList = null, Type SearchType = null)
{
// Set Interfaces[] array size accordingly. (Will be size of our passed InterfaceList, or 1 if InterfaceList is not passed.)
Type[] Interfaces = new Type[InterfaceList == null ? 1 : InterfaceList.Count];
// If our InterfaceList was not set, get all public properties.
if (InterfaceList == null)
Interfaces[0] = source.GetType();
else // Otherwise, get only the public properties from our passed InterfaceList
for (int i = 0; i < InterfaceList.Count; i++)
Interfaces[i] = source.GetType().GetInterface(InterfaceList[i].Name);
IEnumerable<PropertyInfo> propertyList = Enumerable.Empty<PropertyInfo>();
foreach (Type face in Interfaces)
{
if (face != null)
{
// If our SearchType is null, just get all properties that are not already empty
if (SearchType == null)
propertyList = face.GetProperties().Where(prop => prop != null);
else // Otherwise, get all properties that match our SearchType
propertyList = face.GetProperties().Where(prop => prop.PropertyType == SearchType);
// Reset each property
foreach (var property in propertyList)
{
if (property.CanRead && property.CanWrite)
property.SetValue(source, null, new object[] { });
}
}
else
{
// Throw an error or a warning, depends how strict you want to be I guess.
Debug.Log("Warning: Passed interface does not belong to object.");
//throw new Exception("Warning: Passed interface does not belong to object.");
}
}
}
And it's use:
// Clears all properties in object
ClearProperties(Obj);
// Clears all properties in object from MyInterface1 & MyInterface2
ClearProperties(Obj, new List<Type>(){ typeof(MyInterface1), typeof(MyInterface2)});
// Clears all integer properties in object from MyInterface1 & MyInterface2
ClearProperties(Obj, new List<Type>(){ typeof(MyInterface1), typeof(MyInterface2)}, typeof(int));
// Clears all integer properties in object
ClearProperties(Obj,null,typeof(int));
You'd probably need to save the values off in private fields, so that they can be restored later. Maybe something like this:
public class Truck
{
private static const string defaultName = "Super Truck";
private static const int defaultTires = 4;
// Use properties for public members (not public fields)
public string Name { get; set; }
public int Tires { get; set; }
public Truck()
{
Name = defaultName;
Tires = defaultTires;
}
public void ResetTruck()
{
Name = defaultName;
Tires = defaultTires;
}
}
You're essentially looking for the State Design Pattern
If you want a specific past "state" of your object you can create a particular save point to return every time you want. This also let you have a diferent state to backup for everey instance that you create. If you class has many properties who are in constant change, this could be your solution.
public class Truck
{
private string _Name = "Super truck";
private int _Tires = 4;
public string Name
{
get { return _Name; }
set { _Name = value; }
}
public int Tires
{
get { return _Tires; }
set { _Tires = value; }
}
private Truck SavePoint;
public static Truck CreateWithSavePoint(string Name, int Tires)
{
Truck obj = new Truck();
obj.Name = Name;
obj.Tires = Tires;
obj.Save();
return obj;
}
public Truck() { }
public void Save()
{
SavePoint = (Truck)this.MemberwiseClone();
}
public void ResetTruck()
{
Type type = this.GetType();
PropertyInfo[] properties = type.GetProperties();
for (int i = 0; i < properties.Count(); ++i)
properties[i].SetValue(this, properties[i].GetValue(SavePoint));
}
}
If you aren't using a Code Generator or a Designer that would conflict, another option is to go through C#'s TypeDescriptor stuff, which is similar to Reflection, but meant to add more meta information to a class than Reflection could.
using System.ComponentModel;
public class Truck {
// You can use the DefaultValue Attribute for simple primitive properites
[DefaultValue("Super Truck")]
public string Name { get; set; } = "Super Truck";
// You can use a Reset[PropertyName]() method for more complex properties
public int Tires { get; set; } = 4;
public void ResetTires() => Tires = 4;
public Truck() { }
public void ResetTruck() {
// Iterates through each property and tries to reset it
foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(GetType())) {
if (prop.CanResetValue(this)) prop.ResetValue(this);
}
}
}
Note that ResetValue will also reset to a shadowed property if one exists. The priority of which option is selected is explained in the docs:
This method determines the value to reset the property to in the following order of precedence:
There is a shadowed property for this property.
There is a DefaultValueAttribute for this property.
There is a "ResetMyProperty" method that you have implemented, where "MyProperty" is the name of the property you pass to it.
You may represent an object state as a struct or record struct and then set the state to the default value in the Reset method like this:
public class Truck {
record struct State(string Name, int Tires);
private static readonly State _defaultState = new("Super Truck", 4);
private State _state = _defaultState;
public string Name => _state.Name;
public int Tires => _state.Tires;
public Truck() {}
public void ResetTruck() => _state = _defaultState;
}
It is probably the fastest way as well.
Also, a record struct will give you the trivial implementations of the ToString, Equals, GetHashCode.