I recently migrated to new version of protobuf-net, and i started getting this error message after
Repeated data (a list, collection, etc) has inbuilt behaviour and cannot be used as a subclass
Call Stack Trace
protobuf-net.dll!ProtoBuf.Meta.MetaType.AddSubType(int fieldNumber = 1, System.Type derivedType = {Name = "InfoColumn`1" FullName = "Om.Common.InfoSet.InfoColumn`1[[System.Double, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}) Line 83 C#
protobuf-net.dll!ProtoBuf.Meta.MetaType.ApplyDefaultBehaviour() Line 431 + 0x32 bytes C#
Any help in this regard is appreciated. I am planning to rollback my code to previous version of protobuf-net
Below is the class info.
[DataContract]
[ProtoInclude(1, typeof(InfoColumn<Double>))]
[ProtoInclude(2, typeof(InfoColumn<String>))]
[ProtoInclude(3, typeof(InfoColumn<DateTime>))]
[ProtoInclude(4, typeof(InfoColumn<Boolean>))]
public abstract class IInfoColumnBase
{
[DataMember(Order = 101)]
public abstract bool IsSingleValue { get; set; }
[DataMember(Order = 102)]
public abstract string Name { get; set; }
[DataMember(Order = 103)]
public abstract InfoColumnDataType DataType { get; set; }
public abstract long Insert();
public abstract void Insert(long index);
public abstract void SetValue(long index, object val);
public abstract void CopyValues(long start, long end, IInfoColumnBase destCol, long index);
public abstract long GetIndex(object val);
public abstract void Remove(long index);
public abstract object GetValue(long index);
public abstract object GetInternalArrayValue(long index);
public abstract void Clear();
public abstract long Count { get; }
public abstract long ArrayCount { get; }
}
public interface IInfoColumn<T> : IEnumerable<T>
{
T this[double index] { get; set; }
InfoTable Table { get; set; }
double Add(T item);
}
[DataContract(Name = "InfoColumn{0}")]
[KnownType(typeof(InfoColumn<double>))]
[KnownType(typeof(InfoColumn<String>))]
[KnownType(typeof(InfoColumn<bool>))]
[KnownType(typeof(InfoColumn<DateTime>))]
public class InfoColumn<T> : IInfoColumnBase, IInfoColumn<T>
{
long counter = 0;
[DataMember(Order = 1)]
public IList<T> Values { get; set; }
//[DataMember(Order = 2)]
bool isSingleVal = false;
//[DataMember(Order=3)]
public override string Name { get; set; }
//[DataMember(Order=4)]
public override InfoColumnDataType DataType { get; set; }
public InfoTable Table { get; set; }
public override long Count
{
get
{
return this.Table.Count;
}
}
public override long ArrayCount
{
get { return this.Values.Count; }
}
public InfoColumn()
{
}
public InfoColumn(string name,InfoTable table)
{
this.Values = new List<T>();
this.Name = name;
this.Table = table;
}
public override void Clear()
{
this.Values = new List<T>();
}
public override void Remove(long index)
{
int newindex = (int)index;
this.Values.RemoveAt(newindex);
}
public override void CopyValues(long start, long end, IInfoColumnBase destCol, long startIndex)
{
InfoColumn<T> typeCol = destCol as InfoColumn<T>;
for (long ctr = start; ctr <= end; ctr++)
{
typeCol.SetValue(startIndex, this.Values[(int)ctr]);
startIndex++;
}
}
public override void Insert(long rows)
{
if (this.IsSingleValue == true) return;
for (int ctr = 0; ctr < rows; ctr++)
{
this.Values.Add(default(T));
}
}
public T this[double a]
{
get
{
if (a >= this.Count) throw new IndexOutOfRangeException();
long index = (long)a;
if (this.Table.IsFreezed == false)
index = this.Table.CheckData(a);
if (this.isSingleVal == true)
return this.Values[0];
else
return this.Values[(int)index];
}
set
{
if (a >= this.Count) throw new IndexOutOfRangeException();
long index = (long)a;
if (this.Table.IsFreezed == false)
index = this.Table.CheckData(a);
if (this.isSingleVal == true)
this.Values[0] = value;
else
this.Values[(int)index] = value;
}
}
public override long GetIndex(object val)
{
T item = (T)val;
return this.Values.IndexOf(item);
}
public override void SetValue(long index, object val)
{
if (val is InfoSetLink)
this.Values[(int)index] = (T)val;
else
this.Values[(int)index] = (T)Convert.ChangeType(val, typeof(T));
}
public override object GetValue(long index)
{
return this[index];
}
public override object GetInternalArrayValue(long index)
{
return this.Values[(int)index];
}
//[DataMember(Order=5)]
public override bool IsSingleValue
{
get { return isSingleVal; }
set
{
if (isSingleVal == true)
{
this.Values = new List<T>(1);
}
}
}
public override long Insert()
{
if (this.IsSingleValue == true) return -1;
this.Values.Add(default(T));
return this.Values.Count - 1;
}
public double Add(T item)
{
this.Values.Add(item);
return this.Values.Count - 1;
}
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return new InfoColumnEnumerator<T>(this);
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return new InfoColumnEnumerator<T>(this);
}
#endregion
}
InfoColumn<T> has a public Add(T) and implements IEnumerable<T> (via IInfoColumn<T>).
There is wider support for list-like types in v2, and it may be that it is trying to interpret the above as a list. Which indeed, it does look a lot like! I will try to take a look to see if this general scenario can be detected and avoided, but it is an edge case (since it is indeed very list-esque).
There is an existing IgnoreListBehaviour switch, however when validating this for the model shown above, it seems that for this specific scenario the "you can't do that" fires before the code that disables list handling; I have changed this in the source, and this will be included in the next release. Basically, you can address this by adding:
[ProtoContract(IgnoreListHandling = true)]
to the impacted type (InfoColumn<T>), with the next build. Which will be shortly, as soon as I've completed validation etc.
Related
I have 3 interfaces.
public interface IItem
{
string Name { get; set; }
}
public interface IEquipable : IItem
{
void Equip();
}
public interface IConsumable : IItem
{
void Use();
}
IEquipable is implemented by the classes Helmet and Bow, and IConsumable is implemented by classes Potion and Food.
Then, I have a class with a property which contains a List of IItem, and proceed to add a few items of both IEquipable and IConsumable after instantiating it.
public class Character
{
public List<IItem> Items { get; private set; }
public Character()
{
this.Items = new List<IItem>();
}
public void AddItem(IItem item)
{
this.Items.Add(item);
}
}
Program.cs
...
Character char = new Character();
char.AddItem(new Potion());
char.AddItem(new Food());
char.AddItem(new Helmet());
char.AddItem(new Bow());
...
Is there a way I can get a List of all IEquipable members from the List of IItems, each AS IEquipable?
I want to do something like
...
List<IEquipable> equipmentList = //do something to char.Items and get all items of type IEquipable.
IEquipment equipment = equipmentList.First(...)
equipment.Equip();
...
I've tried using List<IEquipable> equipmentList = char.Items.OfType<IEquipable>().ToList() but the resulting list ends up empty.
I implemented (and fixed minor typos in) your code like this:
void Main()
{
Character character = new Character();
character.AddItem(new Potion());
character.AddItem(new Food());
character.AddItem(new Helmet());
character.AddItem(new Bow());
List<IEquipable> equipmentList = character.Items.OfType<IEquipable>().ToList();
}
public class Potion : IConsumable
{
public string Name { get; set; }
public void Use()
{
throw new NotImplementedException();
}
}
public class Food : IConsumable
{
public string Name { get; set; }
public void Use()
{
throw new NotImplementedException();
}
}
public class Helmet : IEquipable
{
public string Name { get; set; }
public void Equip()
{
throw new NotImplementedException();
}
}
public class Bow : IEquipable
{
public string Name { get; set; }
public void Equip()
{
throw new NotImplementedException();
}
}
public interface IItem
{
string Name { get; set; }
}
public interface IEquipable : IItem
{
void Equip();
}
public interface IConsumable : IItem
{
void Use();
}
public class Character
{
public List<IItem> Items { get; private set; }
public Character()
{
this.Items = new List<IItem>();
}
public void AddItem(IItem item)
{
this.Items.Add(item);
}
}
Your exact code (albeit char renamed to character) works perfectly fine. The equipmentList ends up with two elements. The issue you're seeing, i.e. "the resulting list ends up empty", is not reproducible with the code you've posted.
You can use the OfType method
Filters the elements of an IEnumerable based on a specified type.
Signature
public static IEnumerable<TResult> OfType<TResult> (this IEnumerable source)
Usage
var equipable = Character.Items.OfType<IEquipable>();
Or encapsulate it as a method in the instance or an extension method if you like
So it does work like I wanted. My actual code just had another issue and I'm a dummy for not actually posting that. So here it is, for future reference.
using System.Collections.Generic;
using RolePlayGame.Library.Items.Backstage;
using System.Linq;
using System.Text;
using System;
namespace RolePlayGame.Library.Characters.Backstage
{
public class Inventory
{
public List<IItem> StoredItems { get; private set; }
public List<EquippedItem> Gear { get; private set; }
public Inventory()
{
this.StoredItems = new List<IItem>();
this.Gear = new List<EquippedItem>();
}
public bool HasItem(string name)
{
return this.StoredItems.Exists(item => item.Name == name);
}
public bool HasItem(IItem item)
{
return this.StoredItems.Contains(item);
}
public void RemoveItem(string name)
{
int firstIndex = this.StoredItems.FindIndex(item => item.Name == name);
if (firstIndex != -1)
{
this.StoredItems.RemoveAt(firstIndex);
}
}
public void RemoveItem(IItem item)
{
int firstIndex = this.StoredItems.IndexOf(item);
if (firstIndex != -1)
{
this.StoredItems.RemoveAt(firstIndex);
}
}
public void AddItem(IItem item, int quantity)
{
for (int i = 0; i < quantity; i++)
{
this.StoredItems.Add(item);
}
}
public void AddItem(IItem item)
{
this.StoredItems.Add(item);
}
public bool CheckEquipmentSlot(EquipmentSlot slot)
{
return this.Gear.Exists(item => item.UsedSlots.Contains(slot));
}
public bool HasEquipment(IEquipment equipment)
{
return this.Gear.Exists(item => item.Item == equipment);
}
public void AddEquipment(IEquipment equipment)
{
IEquipment alreadyEquipped;
foreach (EquipmentSlot slot in equipment.SlotsUsed)
{
if (this.Gear.Exists(item => item.UsedSlots.Contains(slot)))
{
alreadyEquipped = this.Gear.Find(item => item.UsedSlots.Contains(slot)).Item;
this.RemoveEquipment(slot);
this.StoredItems.Add(alreadyEquipped);
}
}
EquippedItem newEquipment = new EquippedItem(equipment);
this.Gear.Add(newEquipment);
}
public void RemoveEquipment(EquipmentSlot slot)
{
this.Gear.RemoveAll(equipment => equipment.UsedSlots.Contains(slot));
}
public int GetAttributeBonusTotal(AttributeType attribute)
{
int bonusTotal = 0;
foreach (IEquipment item in this.StoredItems.OfType<IEquipment>().ToList())
{
bonusTotal += item.GetAttributeBonus(attribute);
}
return bonusTotal;
}
public int GetCarryWeight()
{
int totalWeight = 0;
foreach (IItem item in StoredItems)
{
totalWeight += item.Weight;
}
return totalWeight;
}
public string GearToString()
{
StringBuilder builder = new StringBuilder();
builder.Append(" Equipped Gear:");
foreach (EquippedItem equipment in this.Gear)
{
builder.Append($"\n {equipment.Item.Name}");
}
return builder.ToString();
}
public string ItemsToString()
{
StringBuilder builder = new StringBuilder();
builder.Append(" Inventory:");
foreach (IItem item in this.StoredItems.Distinct())
{
builder.Append($"\n {item.Name} x {this.StoredItems.FindAll(value => value == item).Count()}");
}
return builder.ToString();
}
public int GetDefenseRateAgainstTypeTotal(DamageType againstType)
{
int rate = 0;
List<IOutfit> outfits = this.Gear.Select(value => value.Item).OfType<IOutfit>().ToList();
foreach (IOutfit item in outfits)
{
rate += item.GetDefenseRateAgainstType(againstType);
}
return rate;
}
}
}
One of the last lines has the problem (now fixed). List<IOutfit> outfits = this.Gear.Select(value => value.Item).OfType<IOutfit>().ToList(); used to be List<IOutfit> outfits = this.Gear.OfType<IOutfit>().ToList();. But Gear is of type List<EquippedItem>, and EquippedItem is not an implementation of IItem.
Here is EquippedItem.cs
using RolePlayGame.Library.Items.Backstage;
using System.Collections.Generic;
namespace RolePlayGame.Library
{
public class EquippedItem
{
public List<EquipmentSlot> UsedSlots { get; set; }
public IEquipment Item { get; set; }
public EquippedItem(IEquipment equipment)
{
this.Item = equipment;
this.UsedSlots = equipment.SlotsUsed;
}
}
}
I needed to select the Item property from the items inside Gear as another list before doing the type filtering with .OfType<IOutfit>(). That's where .Select(value => value.Item) enters the stage.
So that's that. I'll learn to post actual code for future questions.
I red a few articles on internet but all value to me, I couldn't understand how can I avoid adding a duplicate object to a list, I tried something like this.
I actually have created a class which overrides GetHashCode and Equal method.
Now I want to form a collection of non duplicate object list.
public class FlightInfo
{
public string Origin { get; set; }
public string DepartureTime { get; set; }
public string Destination { get; set; }
public string DestinationTime { get; set; }
public string Price { get; set; }
public override bool Equals(object obj)
{
var other = obj as FlightInfo;
if (other == null)
return false;
if (Origin != other.Origin || DepartureTime != other.DepartureTime || Destination != other.Destination
|| DestinationTime != other.DestinationTime || Price != other.Price)
return false;
return true;
}
public override int GetHashCode()
{
int hashOrigin = Origin.GetHashCode();
int hashDestination = Destination.GetHashCode();
int hashDepartureTime = DepartureTime.GetHashCode();
int hashDestinationTime = DestinationTime.GetHashCode();
int hashPrice = Price.GetHashCode();
return hashOrigin ^ hashDestination ^ hashDepartureTime ^ hashDestinationTime ^ hashPrice;
}
}
I also tried one article by Eric
https://blogs.msdn.microsoft.com/ericlippert/2011/02/28/guidelines-and-rules-for-gethashcode/
but this article has
private List<T>[] buckets = new List<T>[100];
insead of private List<T>() buckets = new List<T>()
but I want to return a list with no fix size.
Since you already implemented the Equals and GetHashCode methods you can have your own custom list of FlightInfo that will make use of those methods:
public class FlightInfoList : IList<FlightInfo>
{
private readonly List<FlightInfo> _flightInfos = new List<FlightInfo>();
public IEnumerator<FlightInfo> GetEnumerator()
{
return _flightInfos.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(FlightInfo item)
{
if (_flightInfos.Any(flightInfo => flightInfo.Equals(item)))
{
throw new Exception("Cannot add duplicated values!");
}
_flightInfos.Add(item);
}
public void Clear()
{
_flightInfos.Clear();
}
public bool Contains(FlightInfo item)
{
return _flightInfos.Contains(item);
}
public void CopyTo(FlightInfo[] array, int arrayIndex)
{
_flightInfos.CopyTo(array, arrayIndex);
}
public bool Remove(FlightInfo item)
{
return _flightInfos.Remove(item);
}
public int Count => _flightInfos.Count;
public bool IsReadOnly => false;
public int IndexOf(FlightInfo item)
{
return _flightInfos.IndexOf(item);
}
public void Insert(int index, FlightInfo item)
{
_flightInfos.Insert(index, item);
}
public void RemoveAt(int index)
{
_flightInfos.RemoveAt(index);
}
public FlightInfo this[int index]
{
get => _flightInfos[index];
set => _flightInfos[index] = value;
}
}
Notice that in the Add method I'm checking if there's a duplicated. Another way to solve this is to use a dictionary.
Create three small classes unrelated by inheritance—classes Building, Car and Bicycle. Write an interface ICarbonFootprint with a GetCarbonFootprint method. Have each of your classes implement that interface, so that its GetCarbonFootprint method calculates an appropriate carbon footprint for that class (check out a few websites that explain how to calculate carbon footprints). Write an app that creates objects of each of the three classes, places references to those objects in List, then iterates through the List, polymorphically invoking each object’s GetCarbonFootprint method. Constructor of Car initialize “gallon of gas”, and the Building constructor will initialize buiding-square-footage.
how to calculate carbon-footprint
One gallon of gas yields 20 pounds of CO2 for a car
Multiply the square footage by 50 for a building
None for a bicycle
My instructor's code:
public static void Main(string[] args)
{
ICarbonFootprint[] list = new ICarbonFootprint[3];
// add elements to list
list[0] = new Bicycle();
list[1] = new Building(2500);
list[2] = new Car(10);
// display carbon footprint of each object
for (int i = 0; i < list.Length; i++)
list[i].GetCarbonFootprint();
} // end Main
}
My code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Miller
{
class Program
{
static void Main(string[] args)
{
Bicycle bike = new Bicycle();
Building b = new Building();
Car car = new Car();
List<ICarbonFootprint> list = new List<ICarbonFootprint>();
list.Add(bike);
list.Add(b);
list.Add(car);
int totalCarbon = 0;
foreach (var item in list)
{
totalCarbon += item.GetCarbonFootprint();
Console.WriteLine("{0} has a footprint of: {1}", item, item.GetCarbonFootprint());
}
Console.WriteLine("Total footprint is: {0}", totalCarbon);
Console.ReadKey();
}
}
public class Bicycle : ICarbonFootprint
{
private string _make;
private string _model;
public string Make
{
get { return _make; }
set { _make = value; }
}
public string Model
{
get { return _model; }
set { _model = value; }
}
public int GetCarbonFootprint()
{
return 10;
}
public override string ToString()
{
return string.Format("Bike");
}
}
public class Building : ICarbonFootprint
{
private string _address;
public string Address
{
get { return _address; }
set { _address = value; }
}
public int GetCarbonFootprint()
{
return 2000;
}
public override string ToString()
{
return string.Format("Building");
}
}
public class Car : ICarbonFootprint
{
private string _make;
private string _model;
public string Make
{
get { return _make; }
set { _make = value; }
}
public string Model
{
get { return _model; }
set { _model = value; }
}
public int GetCarbonFootprint()
{
return 1500;
}
public override string ToString()
{
return string.Format("Car");
}
}
public interface ICarbonFootprint
{
int GetCarbonFootprint();
}
}
Me integrating my instructor's code (lines 12-23 changed AKA class Program was the only thing changed):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Miller
{
class Program
{
public static void Main(string[] args)
{
ICarbonFootprint[] list = new ICarbonFootprint[3];
// add elements to list
list[0] = new Bicycle();
list[1] = new Building(2500);
list[2] = new Car(10);
// display carbon footprint of each object
for (int i = 0; i < list.Length; i++)
list[i].GetCarbonFootprint();
} // end Main
}
public class Bicycle : ICarbonFootprint
{
private string _make;
private string _model;
public string Make
{
get { return _make; }
set { _make = value; }
}
public string Model
{
get { return _model; }
set { _model = value; }
}
public int GetCarbonFootprint()
{
return 10;
}
public override string ToString()
{
return string.Format("Bike");
}
}
public class Building : ICarbonFootprint
{
private string _address;
public string Address
{
get { return _address; }
set { _address = value; }
}
public int GetCarbonFootprint()
{
return 2000;
}
public override string ToString()
{
return string.Format("Building");
}
}
public class Car : ICarbonFootprint
{
private string _make;
private string _model;
public string Make
{
get { return _make; }
set { _make = value; }
}
public string Model
{
get { return _model; }
set { _model = value; }
}
public int GetCarbonFootprint()
{
return 1500;
}
public override string ToString()
{
return string.Format("Car");
}
}
public interface ICarbonFootprint
{
int GetCarbonFootprint();
}
}
So, replacing my code for class Program with my instructor's code, I received the following errors:
Program.cs(51,23,51,41): error CS1729: 'Miller.Building' does not contain a constructor that takes 1 arguments
Program.cs(52,23,52,34): error CS1729: 'Miller.Car' does not contain a constructor that takes 1 arguments
Now, because the last two days before Spring break were cancelled due to the weather (snow), we weren't able to discuss. My code seems to do what the directions ask, but I would like to get my instructor's code for class Program working with my code. Could someone help me with these errors possibly?
There are a few issues with your code.
First up you need to include the constructors to make the code compile.
For Building this would look like:
private int squareFootage;
public Building(int squareFootage)
{
this.squareFootage = squareFootage;
}
And for Car this would look like:
private int gasGallons;
public Car(int gasGallons)
{
this.gasGallons = gasGallons;
}
Next, you're not following the rules for calculating the carbon footprint.
They should be:
//Bicycle
public int GetCarbonFootprint()
{
return 0;
}
//Building
public int GetCarbonFootprint()
{
return 50 * squareFootage;
}
//Car
public int GetCarbonFootprint()
{
return 20 * gasGallons;
}
Finally, your instructor's code doesn't actually display any results. The code in the for loop should be changed to be Console.WriteLine(list[i].GetCarbonFootprint()); if this is a console app.
So, all up the code should look like this:
public static void Main(string[] args)
{
ICarbonFootprint[] list = new ICarbonFootprint[3];
// add elements to list
list[0] = new Bicycle();
list[1] = new Building(2500);
list[2] = new Car(10);
// display carbon footprint of each object
for (int i = 0; i < list.Length; i++)
Console.WriteLine(list[i].GetCarbonFootprint());
}
public class Bicycle : ICarbonFootprint
{
public string Make { get; set; }
public string Model { get; set; }
public int GetCarbonFootprint()
{
return 0;
}
}
public class Building : ICarbonFootprint
{
private int squareFootage;
public Building(int squareFootage)
{
this.squareFootage = squareFootage;
}
public string Address { get; set; }
public int GetCarbonFootprint()
{
return 50 * squareFootage;
}
}
public class Car : ICarbonFootprint
{
private int gasGallons;
public Car(int gasGallons)
{
this.gasGallons = gasGallons;
}
public string Make { get; set; }
public string Model { get; set; }
public int GetCarbonFootprint()
{
return 20 * gasGallons;
}
}
public interface ICarbonFootprint
{
int GetCarbonFootprint();
}
I've opted to short-cut the property definitions rather than implement them with fields.
The output is:
0
125000
200
You should write constructors for Building and Car like next:
public Building(int MyValue)
{
...
}
and your code will work fine.
Suggestion: Car and Bicycle shares properties, and the ICarbonFootprint implementation, so you can create a base class with an abstract method. Also the GetCarbonFootprint from ICarbonFootprint interface must be type of System.Double.
public interface ICarbonFootprint
{
int GetCarbonFootprint();
}
public class Building : ICarbonFootprint
{
public int BuildingSquareFootage { get; set; }
public string Address { get; set; }
public Building(int buildingSquareFootage, string address)
{
BuildingSquareFootage = buildingSquareFootage;
Address = address;
}
public int GetCarbonFootprint()
{
return BuildingSquareFootage * 50;
}
public override string ToString()
{
return string.Format("Building");
}
}
public abstract class CarBicycleBase : ICarbonFootprint
{
public string Make { get; set; }
public string Model { get; set; }
protected CarBicycleBase(string make, string model)
{
Make = make;
Model = model;
}
public abstract int GetCarbonFootprint();
}
public class Bicycle : CarBicycleBase
{
public Bicycle(string make, string model)
: base(make, model) { }
public override int GetCarbonFootprint()
{
return 0;
}
public override string ToString()
{
return string.Format("Bike");
}
}
public class Car : CarBicycleBase
{
public int GallonOfGas { get; set; }
public Car(int gallonOfGas, string make, string model)
: base(make, model)
{
GallonOfGas = gallonOfGas;
}
public override int GetCarbonFootprint()
{
return GallonOfGas * 20;
}
public override string ToString()
{
return string.Format("Car");
}
}
Example:
...
var list = new List<ICarbonFootprint>(3)
{
new Car(10, "...", "..."),
new Bicycle("...", "..."),
new Building(20, "...")
};
foreach (ICarbonFootprint item in list)
item.GetCarbonFootprint();
...
I hope it helps.
I'm trying to control the depth of generation of an object tree with Autofixture. In some cases I want just to generate the root object and in another set of cases I may want to generate the tree up to a certain depth (2, 3, let's say).
class Foo {
public string Name {get;set;}
public Bar Bar {get;set;}
public AnotherType Xpto {get;set;}
public YetAnotherType Xpto {get;set;}
}
class Bar {
public string Name {get;set;}
public string Description {get;set;}
public AnotherType Xpto {get;set;}
public YetAnotherType Xpto {get;set;}
public Xpto Xpto {get;set;}
}
class Xpto {
public string Description {get;set;}
public AnotherType Xpto {get;set;}
public YetAnotherType Xpto {get;set;}
}
With the example above I would want (depth 1) to control the generation process so that only the Foo class is instantiated and the Bar property or any other reference type on that class is not populated or (depth 2) I would want the Foo class instantiated, the Bar property populated with a new instance of Bar but the Xpto property or any other reference type on that class not populated.
In case I did not spot it in the codebase does Autofixture have a customisation or behaviour to allow us to have that kind of control?
Again, it's not recursion that I want to control but the depth of population of the object graph.
No Bar
One-off:
var f = fixture.Build<Foo>().Without(f => f.Bar).Create();
Reusable:
fixture.Customize<Foo>(c => c.Without(f => f.Bar));
var f = fixture.Create<Foo>();
No Xpto
One-off:
var f = fixture
.Build<Foo>()
.With(
f => f.Bar,
fixture.Build<Bar>().Without(b => b.Xpto).Create())
.Create();
Reusable:
fixture.Customize<Bar>(c => c.Without(b => b.Xpto));
var f = fixture.Create<Foo>();
You can use the below GenerationDepthBehavior class as follows:
fixture.Behaviors.Add(new GenerationDepthBehavior(2));
public class GenerationDepthBehavior : ISpecimenBuilderTransformation
{
private const int DefaultGenerationDepth = 1;
private readonly int generationDepth;
public GenerationDepthBehavior() : this(DefaultGenerationDepth)
{
}
public GenerationDepthBehavior(int generationDepth)
{
if (generationDepth < 1)
throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");
this.generationDepth = generationDepth;
}
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
return new GenerationDepthGuard(builder, new GenerationDepthHandler(), this.generationDepth);
}
}
public interface IGenerationDepthHandler
{
object HandleGenerationDepthLimitRequest(object request, IEnumerable<object> recordedRequests, int depth);
}
public class DepthSeededRequest : SeededRequest
{
public int Depth { get; }
public int MaxDepth { get; set; }
public bool ContinueSeed { get; }
public int GenerationLevel { get; private set; }
public DepthSeededRequest(object request, object seed, int depth) : base(request, seed)
{
Depth = depth;
Type innerRequest = request as Type;
if (innerRequest != null)
{
bool nullable = Nullable.GetUnderlyingType(innerRequest) != null;
ContinueSeed = nullable || innerRequest.IsGenericType;
if (ContinueSeed)
{
GenerationLevel = GetGenerationLevel(innerRequest);
}
}
}
private int GetGenerationLevel(Type innerRequest)
{
int level = 0;
if (Nullable.GetUnderlyingType(innerRequest) != null)
{
level = 1;
}
if (innerRequest.IsGenericType)
{
foreach (Type generic in innerRequest.GetGenericArguments())
{
level++;
level += GetGenerationLevel(generic);
}
}
return level;
}
}
public class GenerationDepthGuard : ISpecimenBuilderNode
{
private readonly ThreadLocal<Stack<DepthSeededRequest>> requestsByThread
= new ThreadLocal<Stack<DepthSeededRequest>>(() => new Stack<DepthSeededRequest>());
private Stack<DepthSeededRequest> GetMonitoredRequestsForCurrentThread() => this.requestsByThread.Value;
public GenerationDepthGuard(ISpecimenBuilder builder)
: this(builder, EqualityComparer<object>.Default)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler)
: this(
builder,
depthHandler,
EqualityComparer<object>.Default,
1)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
int generationDepth)
: this(
builder,
depthHandler,
EqualityComparer<object>.Default,
generationDepth)
{
}
public GenerationDepthGuard(ISpecimenBuilder builder, IEqualityComparer comparer)
{
this.Builder = builder ?? throw new ArgumentNullException(nameof(builder));
this.Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
this.GenerationDepth = 1;
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
IEqualityComparer comparer)
: this(
builder,
depthHandler,
comparer,
1)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
IEqualityComparer comparer,
int generationDepth)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
if (depthHandler == null) throw new ArgumentNullException(nameof(depthHandler));
if (comparer == null) throw new ArgumentNullException(nameof(comparer));
if (generationDepth < 1)
throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");
this.Builder = builder;
this.GenerationDepthHandler = depthHandler;
this.Comparer = comparer;
this.GenerationDepth = generationDepth;
}
public ISpecimenBuilder Builder { get; }
public IGenerationDepthHandler GenerationDepthHandler { get; }
public int GenerationDepth { get; }
public int CurrentDepth { get; }
public IEqualityComparer Comparer { get; }
protected IEnumerable RecordedRequests => this.GetMonitoredRequestsForCurrentThread();
public virtual object HandleGenerationDepthLimitRequest(object request, int currentDepth)
{
return this.GenerationDepthHandler.HandleGenerationDepthLimitRequest(
request,
this.GetMonitoredRequestsForCurrentThread(), currentDepth);
}
public object Create(object request, ISpecimenContext context)
{
if (request is SeededRequest)
{
int currentDepth = 0;
var requestsForCurrentThread = GetMonitoredRequestsForCurrentThread();
if (requestsForCurrentThread.Count > 0)
{
currentDepth = requestsForCurrentThread.Max(x => x.Depth) + 1;
}
DepthSeededRequest depthRequest = new DepthSeededRequest(((SeededRequest)request).Request, ((SeededRequest)request).Seed, currentDepth);
if (depthRequest.Depth >= GenerationDepth)
{
var parentRequest = requestsForCurrentThread.Peek();
depthRequest.MaxDepth = parentRequest.Depth + parentRequest.GenerationLevel;
if (!(parentRequest.ContinueSeed && currentDepth < depthRequest.MaxDepth))
{
return HandleGenerationDepthLimitRequest(request, depthRequest.Depth);
}
}
requestsForCurrentThread.Push(depthRequest);
try
{
return Builder.Create(request, context);
}
finally
{
requestsForCurrentThread.Pop();
}
}
else
{
return Builder.Create(request, context);
}
}
public virtual ISpecimenBuilderNode Compose(
IEnumerable<ISpecimenBuilder> builders)
{
var composedBuilder = ComposeIfMultiple(
builders);
return new GenerationDepthGuard(
composedBuilder,
this.GenerationDepthHandler,
this.Comparer,
this.GenerationDepth);
}
internal static ISpecimenBuilder ComposeIfMultiple(IEnumerable<ISpecimenBuilder> builders)
{
ISpecimenBuilder singleItem = null;
List<ISpecimenBuilder> multipleItems = null;
bool hasItems = false;
using (var enumerator = builders.GetEnumerator())
{
if (enumerator.MoveNext())
{
singleItem = enumerator.Current;
hasItems = true;
while (enumerator.MoveNext())
{
if (multipleItems == null)
{
multipleItems = new List<ISpecimenBuilder> { singleItem };
}
multipleItems.Add(enumerator.Current);
}
}
}
if (!hasItems)
{
return new CompositeSpecimenBuilder();
}
if (multipleItems == null)
{
return singleItem;
}
return new CompositeSpecimenBuilder(multipleItems);
}
public virtual IEnumerator<ISpecimenBuilder> GetEnumerator()
{
yield return this.Builder;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
public class GenerationDepthHandler : IGenerationDepthHandler
{
public object HandleGenerationDepthLimitRequest(
object request,
IEnumerable<object> recordedRequests, int depth)
{
return new OmitSpecimen();
}
}
This feature was requested in a github issue. It was ultimately rejected. However, it was rejected because there was a nice, simple solution posted within the issue.
public class GenerationDepthBehavior: ISpecimenBuilderTransformation
{
public int Depth { get; }
public GenerationDepthBehavior(int depth)
{
Depth = depth;
}
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
return new RecursionGuard(builder, new OmitOnRecursionHandler(), new IsSeededRequestComparer(), Depth);
}
private class IsSeededRequestComparer : IEqualityComparer
{
bool IEqualityComparer.Equals(object x, object y)
{
return x is SeededRequest && y is SeededRequest;
}
int IEqualityComparer.GetHashCode(object obj)
{
return obj is SeededRequest ? 0 : EqualityComparer<object>.Default.GetHashCode(obj);
}
}
}
You can then use this as follows:
fixture.Behaviors.Add(new GenerationDepthBehavior(2));
I want to implement a command pattern. I have the following:
public class State
{
public int Number { get; set; }
public void Execute(IAction action)
{
if (action.IsValid(this))
action.Apply(this);
}
}
public interface IAction
{
bool IsValid(State state);
void Apply(State state);
}
public class ActionSet5IfZero : IAction
{
public bool IsValid(State state)
{
if (state.Number == 0)
return true;
else
return false;
}
public void Apply(State state)
{
state.Number = 5;
}
}
And the program:
static void Main(string[] args)
{
State s = new State();
s.Execute(new ActionSet5IfZero());
}
That works as expected. My problem begins, when I would like to extend the State class:
public class ExtendedState : State
{
public int Number2 { get; set; }
}
Now the action must apply changes on ExtendedState. So I thought I would create extended action that has two additional functions that take ExtendedState as a parameter:
public class ExtendedActionSet5IfZero : IAction
{
public bool IsValid(State state)
{
throw new NotImplementedException();
}
public void Apply(State state)
{
throw new NotImplementedException();
}
public bool IsValid(ExtendedState state)
{
if (state.Number == 0 && state.Number2 == 0)
return true;
else
return false;
}
public void Apply(ExtendedState state)
{
state.Number = 5;
state.Number2 = 5;
}
}
This is something I already do not like because the functions that implement the interface become redundant. Moreover I need to create a new Execute function in my ExtendedState that utilizes the new type and not IAction (otherwise not implemented functions get called).
I am sure it can be done in a nice OO way. Can you help me out? The aim is to create an extensible State class and IAction interface (maybe even generic, I do not know), so I can extend the State but remain the generic functionality without additional coding.
You could add a virtual SetNumber method to state
public class State
{
public int Number { get; set; }
public virtual void SetNumber(int n)
{
Number = n;
}
public void Execute(IAction action)
{
if (action.IsValid(this))
action.Apply(this);
}
}
In the extended state you orverride it
public class ExtendedState : State {
public int Number2 { get; set; }
public orverride void SetNumber(int n)
{
base.SetNumber(n);
Number2 = n;
}
}
The action would then be implemented like this
public void Apply(State state)
{
state.SetNumber(5);
}
EDIT:
What about declaring Number as array?
public class State
{
public int[] Numbers { get; private set; }
public State()
{
Numbers = new int[1];
}
...
}
The action then does this
public void Apply(State state)
{
for (int i = 0; i < state.Numbers.Length; i++) {
state.Numbers[i] = 5;
}
}
The constructor of ExtendedState would initialize Numbers with
Numbers = new int[2];
In addition, you could have properties for the single numbers
public int Number {
get { return Numbers[0]; }
set { Numbers[0] = value; }
}
and
public int Number2 {
get { return Numbers[1]; }
set { Numbers[1] = value; }
}
You could use generics:
interface IAction<TState> where TState: State
{
bool IsValid(TState state);
void Apply(TState state);
}
How about adding StateContainer to State and Action:
public interface IStateContainer<TState, TAction> where TState : IState where TAction : IAction<TState> {
public TState State;
public void Execute(TAction action);
}
public interface IState { }
public interface IAction<TState> where TState : IState {
bool IsValid(TState state);
void Apply(TState state);
}
Then your original classes can be replaced with:
public class ValidatingStateContainer<TState, TAction> : IStateContainer<TState, TAction> {
public ValidatingStateContainer(TState state) {
State = state;
}
public TState State { get; private set; }
public void Execute(TAction action)
{
if (action.IsValid(this))
action.Apply(State);
}
}
public class ActionSet5IfZero : IAction<NumberState>
{
public boolean IsValid(NumberState state)
{
if (state.Number == 0)
return true;
else
return false;
}
public void Apply(NumberState state)
{
state.Number = 5;
}
}
public class ExtendedActionSet5IfZero : ActionSet5IfZero, IAction<TwoNumberState>
{
public boolean IsValid(TwoNumberState state)
{
if (base.IsValid(state) && state.Number2 == 0)
return true;
else
return false;
}
public void Apply(TwoNumberState state)
{
base.Apply(state);
state.Number2 = 5;
}
}
public class NumberState : IState {
public int Number { get; set; }
}
public class TwoNumberState : NumberState {
public int Number2 { get; set; }
}