0

How can you drop items in a ICollection without knowing its TSource? I thought LINQ would be the best route for this, but I cannot figure out how it might be done with Reflection or any other method.

I am trying to implement this in a class that inherits System.Attribute so generic types are not permitted. And, the returned type information must be the same as the type information received on the value parameter of MutateValue. So obviously, generic would be a joy in this case, but is there another solution.

using System;
using System.Collections;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace Dado.ComponentModel.DataMutations
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class ApplyMaxLengthStackAttribute : MutationAttribute
    {
        private int _maxLength = -1;

        public object Mutate(object value, int maxLength, IMutationContext context = null)
        {
            _maxLength = maxLength;

            return Mutate(value, context);
        }

        protected override object MutateValue(object value, IMutationContext context)
        {
            if (value != null) {
                if (
                    value is string valueAsString &&
                    (
                        _maxLength > -1 || TryGetStringLengthAttributeValue(context, out _maxLength)
                    ) &&
                    valueAsString.Length > _maxLength
                ) {
                    value = valueAsString.Substring(0, _maxLength);
                }
                else if (
                    value is ICollection valueAsCollection &&
                    (
                        _maxLength > -1 || TryGetMaxLengthAttributeValue(context, out _maxLength)
                    ) &&
                    valueAsCollection.Count > _maxLength
                ) {
                    // Error CS1061
                    value = valueAsCollection.Take(_maxLength);
                }
            }

            _maxLength = -1;

            return value;
        }

        private bool TryGetStringLengthAttributeValue(IMutationContext context, out int maxLength)
        {
            var attribute = context?.Attributes.OfType<StringLengthAttribute>().FirstOrDefault();

            if (attribute != null) {
                maxLength = attribute.MaximumLength;

                return true;
            }

            return TryGetMaxLengthAttributeValue(context, out maxLength);
        }

        private bool TryGetMaxLengthAttributeValue(IMutationContext context, out int maxLength)
        {
            var attribute = context?.Attributes.OfType<MaxLengthAttribute>().FirstOrDefault();

            if (attribute != null) {
                maxLength = attribute.Length;

                return true;
            }

            maxLength = -1;

            return false;
        }
    }
}

You will see this code will not compile and throws this error:

Error CS1061: 'ICollection' does not contain a definition for 'Take' and no extension method 'Take' accepting a first argument of type 'ICollection' could be found (are you missing a using directive or an assembly reference?)

This class is an extension to the Dado.ComponentModel.Mutations project. Definitions for other Dado.ComponentModel.Mutations member definitions should be sought there.

roydukkey
  • 3,149
  • 2
  • 27
  • 43

2 Answers2

1

dynamic to the rescue!

value = Enumerable.Take((dynamic)value, _maxLength);

There are few valid reasons to use dynamic in a language with such a powerful type system as C#, but it does have its occasional shortcomings, where the language designers for whatever reason (usually lack of time/resources) didn’t implement some way to express something that should be valid to express and possible for them to implement. However, when you hit what appears to be a rather arbitrary bump (or wall) that is simply an arbitrary limitation of the language, I think that is a perfectly valid reason to briefly abandon the type system, which is basically what dynamic lets you do. I believe you have hit one of those arbitrary limitations and you are perfectly justified in using dynamic in this situation.

If you want to make your code a little more “safe” or fool proof (should some collection that doesn’t implement IEnumerable<> get passed in), you can amend your else if to check if value implements IEnumerable<> (which you’ll have to do via reflection, unfortunately)

Dave M
  • 2,863
  • 1
  • 22
  • 17
0

Many extension methods in LINQ work on type IEnumerable<T> rather than IEnumerable. Take is one such method. Try casting to IEnumerable<object> like this:

valueAsCollection.Cast<object>().Take(_maxLength)
Jason Boyd
  • 6,839
  • 4
  • 29
  • 47
  • `list = (List)attribute.Mutate(list, 3);` resutls in `System.InvalidCastException : Unable to cast object of type 'd__25\`1[System.Object]' to type 'System.Collections.Generic.List\`1[System.Int32]'.` – roydukkey May 13 '18 at 21:09
  • @roydukkey You are asking a new question now; the short answer is that an `IEnumerable` is not an `List`. This is a matter of variance. These links might help: https://stackoverflow.com/q/2184551/1937249, https://stackoverflow.com/q/3445631/1937249. – Jason Boyd May 13 '18 at 21:51
  • @roydukkey I'm not sure what your end goal is but you may need to reconsider your design. You are fighting the language. – Jason Boyd May 13 '18 at 21:53
  • Possibly, but this is due to the limitation of Attributes not allowing generic types. https://github.com/dotnet/csharplang/issues/124 I'll update my answer with more of my code and scenario. – roydukkey May 14 '18 at 00:38