C# passing a string by reference

I'm having trouble with strings and references. My goal is to have string _specificAgentIds to be populated after the processDomainObjectParameter method is called.

The way I pass _specificAgentIds to the method, is by first creating a ParamValuePairs, which is a simple class with two members, one of which being ParamValue of type Object, and then assigning _specificAgentIds to ParamValue. Then I add it to the dictionary, and eventually pass the dictionary to the method for processing. The dictionary should always be passed by reference, so that shouldn't be the problem.

But after the method returns, _specificAgentIds  is still empty. Any ideas why?

public sealed class ReportParameterCollectionValuesHelper
    {
        #region Private Fields
        private const string BeginDateFilterName = "BeginDate";
        private const string EndDateFilterName = "EndDate";
        private const string SpecificAgentsFilterName = "specificAgents";
        private const string SpecificSubmittedByFilterName = "specificSubmittedBy";
        private const string SpecificQuotingForFilterName = "specificQuotingFor";
        private const string SpecificGoverningRegionFilterName = "specificGoverningRegion";

        private RDate _beginDate = RDate.Null;
        private RDate _endDate = RDate.Null;
        private string _specificAgentIds = string.Empty;
        private string _specificSubmittedByIds = string.Empty;
        private string _specificQuotingForIds = string.Empty;
        private string _specificGoverningRegionIds = string.Empty;
        #endregion

        #region Properties
        public RDate BeginDate
        {
            get 
            {
                if (_beginDate == null || _beginDate.IsNull) throw new InvalidOperationException();
                return _beginDate; 
            }
        }

        public RDate EndDate
        {
            get 
            {
                if (_endDate == null || _endDate.IsNull) throw new InvalidOperationException();
                return _endDate; 
            }
        }

        public string SpecificAgentIds
        {
            get 
            {
                if (string.IsNullOrEmpty(_specificAgentIds)) throw new InvalidOperationException();
                return _specificAgentIds; 
            }
        }

        public string SpecificSubmittedByIds
        {
            get 
            {
                if (string.IsNullOrEmpty(_specificSubmittedByIds)) throw new InvalidOperationException();
                return _specificSubmittedByIds; 
            }
        }

        public string SpecificQuotingForIds
        {
            get 
            {
                if (string.IsNullOrEmpty(_specificQuotingForIds)) throw new InvalidOperationException();
                return _specificQuotingForIds; 
            }
        }

        public string SpecificGoverningRegionIds
        {
            get 
            {
                if (string.IsNullOrEmpty(_specificGoverningRegionIds)) throw new InvalidOperationException();
                return _specificGoverningRegionIds;
            }
        }

        private string FilterValues { get; set; }
        #endregion

        #region Ctor
        public ReportParameterCollectionValuesHelper(ReportParameterCollection parameters)
        {
            if (parameters == null) throw new ArgumentNullException();

            Dictionary<string, ParamValuePairs> paramCollection = new Dictionary<string, ParamValuePairs>();

            // Date Params
            paramCollection.Add(BeginDateFilterName, new ParamValuePairs { ParamType = FilterType.DateTime, ParamValue = _beginDate });
            paramCollection.Add(EndDateFilterName, new ParamValuePairs { ParamType = FilterType.DateTime, ParamValue = _endDate });

            // Domain Object (Look-up) Params
            paramCollection.Add(SpecificAgentsFilterName, new ParamValuePairs { ParamType = FilterType.DomainObject, ParamValue = _specificAgentIds });
            paramCollection.Add(SpecificSubmittedByFilterName, new ParamValuePairs { ParamType = FilterType.DomainObject, ParamValue = _specificSubmittedByIds });
            paramCollection.Add(SpecificQuotingForFilterName, new ParamValuePairs { ParamType = FilterType.DomainObject, ParamValue = _specificQuotingForIds });

            // Domain Enum (Drop-down) Params
            //paramCollection.Add(FilterType.DomainEnum, new ParamValuePairs { ParamName = SpecificGoverningRegionFilterName, ParamValue = _specificGoverningRegionIds });

            // Process User Input
            processDomainObjectParameter(parameters, paramCollection);

        }
        #endregion

        #region Methods



        private void processDomainObjectParameter(ReportParameterCollection parameters, Dictionary<string, ParamValuePairs> paramCollection)
        {
            foreach (KeyValuePair<string, ParamValuePairs> pair in paramCollection)
            {
                string paramName = pair.Key;
                ParamValuePairs valuePair = pair.Value;
                FilterType filterType = valuePair.ParamType;

                switch (filterType)
                {
                    case FilterType.DateTime:

                        if ((valuePair.ParamValue is RDate) == false) throw new ArgumentNullException();

                        if (parameters[paramName] != null && parameters[paramName].AsDateTime().Count > 0)
                        {
                            valuePair.ParamValue = new RDate(parameters[paramName].AsDateTime()[0].Date);
                        }

                        break;

                    case FilterType.DomainObject:

                        if ((valuePair.ParamValue is string) == false) throw new ArgumentNullException();

                        if (parameters[paramName] != null && parameters[paramName].AsDomainObject().Count > 0)
                        {
                            valuePair.ParamValue = string.Join(",", parameters[paramName].AsDomainObject().Cast<Entity>().Select(x => x.Id).ToList());

                            FilterValues += string.Format("{0}: {1}{2}",
                                parameters[paramName].DisplayName,
                                string.Join(", ", parameters[paramName].AsDomainObject().Cast<Entity>().Select(x => x.Code + " - " + x.Name1).ToList()),
                                Environment.NewLine);
                        }

                        break;

                    case FilterType.DomainEnum:

                        break;
                }                
            }
        }

        public string GetFilterString()
        {
            string dateFilter = string.Empty;

            if (BeginDate != null && !BeginDate.IsNull && EndDate != null && !EndDate.IsNull)
            {
                dateFilter = string.Format("Date Range from {0} to {1}{2}", BeginDate.ToShortDateString(), EndDate.ToShortDateString(), Environment.NewLine);
            }

            return dateFilter + FilterValues;
        }

        #endregion
    }

    enum FilterType
    {
        Boolean,
        DateTime,
        Decimal,
        DomainEnum,
        DomainObject,
        Integer
    }

    public sealed class ParamValuePairs
    {
        public FilterType ParamType;
        public object ParamValue;
    }

Open in new window

LVL 8
pzozulkaAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

käµfm³d 👽Commented:
Strings are reference types in .NET, but they are always passed by value. The only way you would be able to get the desired by reference behavior would be to pass the string directly, rather than as an item in a ReportParameterCollection or Dictionary, as a ref parameter (or out parameter, depending).
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
pzozulkaAuthor Commented:
So, in other words, to get my desired by reference behavior, instead of loading a collection with 100 items, I would have to call a method 100 times instead (passing the string by ref)?

No other workaround for this? This seems like a huge drawback.
0
Miguel OzSoftware EngineerCommented:
Nope, strings are immutable in .NET and behave as if they are passed by value. Other languages like Java use the same construct.
In your case, _specificAgentIds is never set because is never assigned, my suggestion will be to assign it somewhere in your logic  when you process the parameter collections.

Another performance suggestion: Modify your ParamValuePairs to use generics and avoid casting and As Operator.
    public sealed class ParamValuePairs<T>
    {
        public FilterType ParamType;
        public T ParamValue;
    }

Open in new window

0
Cloud Class® Course: MCSA MCSE Windows Server 2012

This course teaches how to install and configure Windows Server 2012 R2.  It is the first step on your path to becoming a Microsoft Certified Solutions Expert (MCSE).

pzozulkaAuthor Commented:
my suggestion will be to assign it somewhere in your logic

The method that processes the logic needs to be dynamic because otherwise it would have a lot of repetitive code. So for example, while I would love to do something like:

If ( paramName == "BeginDate" )
   _beginDate = parameters["BeginDate"]....
Else If ( paramName == "EndDate" )
   _endDate = parameters["EndDate"]....

This can get pretty repetitive. That's why I was hoping to pass a collection of paramName and the value place holder like paramValue, so that I can perform my logic dynamically.

In other words, instead of the two (or more) if statements above, I can just have one inside a loop, something like:

foreach( var item in collection )
{
   _item.paramValue = parameters[item.paramName]....
}

Any suggestions on how to accomplish this in my code since it doesn't seem like I can pass my collection of strings, paramValue, by reference to my method.
0
Miguel OzSoftware EngineerCommented:
Your suggestion is correct because both collection  and parameters are dictionaries and the value is an object (passed by reference) thus you will always refer to whatever ParamValuePairs instance is assigned.
My only addition will be to do an extra check that the key exists using TryGetValue
foreach( var item in collection )
 {
       ParamValuePairs value;
 	if (d.TryGetValue("item.paramName", out value))
	{
	    _item.paramValue = value;
	}
        //else do some error handling or add default value
 }

Open in new window

0
pzozulkaAuthor Commented:
Ok, I have modified my class to always pass the paramValue by reference, but it's still not working. This is the param that needs to be assigned a value inside the method.

    public sealed class ReportParameterCollectionValuesHelper
    {
        enum FilterType
        {
            Boolean,
            DateTime,
            Decimal,
            DomainEnum,
            DomainObject,
            Integer
        }

        class ParamValuePairs
        {
            public FilterType ParamType;
            public string ParamName;
        }

        #region Private Fields
        private const string BeginDateFilterName = "BeginDate";
        private const string EndDateFilterName = "EndDate";
        private const string SpecificAgentsFilterName = "specificAgents";
        private const string SpecificSubmittedByFilterName = "specificSubmittedBy";
        private const string SpecificQuotingForFilterName = "specificQuotingFor";
        private const string SpecificGoverningRegionFilterName = "specificGoverningRegion";

        private RDate _beginDate = RDate.Null;
        private RDate _endDate = RDate.Null;
        private string _specificAgentIds = string.Empty;
        private string _specificSubmittedByIds = string.Empty;
        private string _specificQuotingForIds = string.Empty;
        private string _specificGoverningRegionIds = string.Empty;
        #endregion

        #region Properties
        public RDate BeginDate
        {
            get 
            {
                if (_beginDate == null || _beginDate.IsNull) throw new InvalidOperationException();
                return _beginDate; 
            }
        }

        public RDate EndDate
        {
            get 
            {
                if (_endDate == null || _endDate.IsNull) throw new InvalidOperationException();
                return _endDate; 
            }
        }

        public string SpecificAgentIds
        {
            get 
            {
                if (string.IsNullOrEmpty(_specificAgentIds)) throw new InvalidOperationException();
                return _specificAgentIds; 
            }
        }

        public string SpecificSubmittedByIds
        {
            get 
            {
                if (string.IsNullOrEmpty(_specificSubmittedByIds)) throw new InvalidOperationException();
                return _specificSubmittedByIds; 
            }
        }

        public string SpecificQuotingForIds
        {
            get 
            {
                if (string.IsNullOrEmpty(_specificQuotingForIds)) throw new InvalidOperationException();
                return _specificQuotingForIds; 
            }
        }

        public string SpecificGoverningRegionIds
        {
            get 
            {
                if (string.IsNullOrEmpty(_specificGoverningRegionIds)) throw new InvalidOperationException();
                return _specificGoverningRegionIds;
            }
        }

        private string FilterValues { get; set; }
        #endregion

        #region Ctor
        public ReportParameterCollectionValuesHelper(ReportParameterCollection parameters)
        {
            if (parameters == null) throw new ArgumentNullException();

            Dictionary<ParamValuePairs, Object> paramCollection = new Dictionary<ParamValuePairs, object>();

            // Date Params
            paramCollection.Add(new ParamValuePairs { ParamType = FilterType.DateTime, ParamName = BeginDateFilterName }, _beginDate);
            paramCollection.Add(new ParamValuePairs { ParamType = FilterType.DateTime, ParamName = EndDateFilterName }, _endDate);

            // Domain Object (Look-up) Params
            paramCollection.Add(new ParamValuePairs { ParamType = FilterType.DomainObject, ParamName = SpecificAgentsFilterName }, _specificAgentIds);
            paramCollection.Add(new ParamValuePairs { ParamType = FilterType.DomainObject, ParamName = SpecificSubmittedByFilterName }, _specificSubmittedByIds);
            paramCollection.Add(new ParamValuePairs { ParamType = FilterType.DomainObject, ParamName = SpecificQuotingForFilterName }, _specificQuotingForIds);

            // Domain Enum (Drop-down) Params
            //paramCollection.Add(FilterType.DomainEnum, new ParamValuePairs { ParamName = SpecificGoverningRegionFilterName, ParamValue = _specificGoverningRegionIds });

            foreach (KeyValuePair<ParamValuePairs, object> pair in paramCollection)
            {
                object paramValue = pair.Value;

                // Process User Input
                processDomainObjectParameter(parameters, pair.Key.ParamType, pair.Key.ParamName, ref paramValue);
            }

        }
        #endregion

        #region Methods

        private void processDomainObjectParameter(ReportParameterCollection parameters, FilterType type, string paramName, ref object paramValue)
        {
            switch (type)
            {
                case FilterType.DateTime:

                    if ((paramValue is RDate) == false) throw new ArgumentNullException();

                    if (parameters[paramName] != null && parameters[paramName].AsDateTime().Count > 0)
                    {
                        paramValue = new RDate(parameters[paramName].AsDateTime()[0].Date);
                    }

                    break;

                case FilterType.DomainObject:

                    if ((paramValue is string) == false) throw new ArgumentNullException();

                    if (parameters[paramName] != null && parameters[paramName].AsDomainObject().Count > 0)
                    {
                        paramValue = string.Join(",", parameters[paramName].AsDomainObject().Cast<Entity>().Select(x => x.Id).ToList());

                        FilterValues += string.Format("{0}: {1}{2}",
                            parameters[paramName].DisplayName,
                            string.Join(", ", parameters[paramName].AsDomainObject().Cast<Entity>().Select(x => x.Code + " - " + x.Name1).ToList()),
                            Environment.NewLine);
                    }

                    break;

                case FilterType.DomainEnum:

                    break;
            }                
        }

        public string GetFilterString()
        {
            string dateFilter = string.Empty;

            if (BeginDate != null && !BeginDate.IsNull && EndDate != null && !EndDate.IsNull)
            {
                dateFilter = string.Format("Date Range from {0} to {1}{2}", BeginDate.ToShortDateString(), EndDate.ToShortDateString(), Environment.NewLine);
            }

            return dateFilter + FilterValues;
        }

        #endregion
    }

Open in new window

0
it_saigeDeveloperCommented:
It almost looks like you are wanting to do some sort of object mapper.  If that is the case then the way you have laid out will not produce the results you are wanting.  You probably need to implement something like (note this is untested and not meant to represent the objects and/or methods you have defined in your environment):
using System;
using System.Reflection;

namespace EE_Q28770879
{
	class Program
	{
		static void Main(string[] args)
		{
		}
	}

	public sealed class ReportParameterCollectionValuesHelper
	{
		#region Private Fields
		private DateTime? _beginDate = null;
		private DateTime? _endDate = null;
		private string _specificAgentIds = string.Empty;
		private string _specificSubmittedByIds = string.Empty;
		private string _specificQuotingForIds = string.Empty;
		private string _specificGoverningRegionIds = string.Empty;
		#endregion

		#region Properties
		public DateTime? BeginDate
		{
			get
			{
				if (_beginDate == null || !_beginDate.HasValue) throw new InvalidOperationException();
				return _beginDate;
			}
		}

		public DateTime? EndDate
		{
			get
			{
				if (_endDate == null || !_endDate.HasValue) throw new InvalidOperationException();
				return _endDate;
			}
		}

		public string SpecificAgentIds
		{
			get
			{
				if (string.IsNullOrEmpty(_specificAgentIds)) throw new InvalidOperationException();
				return _specificAgentIds;
			}
		}

		public string SpecificSubmittedByIds
		{
			get
			{
				if (string.IsNullOrEmpty(_specificSubmittedByIds)) throw new InvalidOperationException();
				return _specificSubmittedByIds;
			}
		}

		public string SpecificQuotingForIds
		{
			get
			{
				if (string.IsNullOrEmpty(_specificQuotingForIds)) throw new InvalidOperationException();
				return _specificQuotingForIds;
			}
		}

		public string SpecificGoverningRegionIds
		{
			get
			{
				if (string.IsNullOrEmpty(_specificGoverningRegionIds)) throw new InvalidOperationException();
				return _specificGoverningRegionIds;
			}
		}

		private string FilterValues { get; set; }
		#endregion

		#region Ctor
		public ReportParameterCollectionValuesHelper(ReportParameterCollection parameters)
		{
			if (parameters == null) throw new ArgumentNullException();
			this.MapParameters(parameters);
		}
		#endregion

		#region Methods
		public string GetFilterString()
		{
			string dateFilter = string.Empty;

			if (BeginDate != null && BeginDate.HasValue && EndDate != null && EndDate.HasValue)
			{
				dateFilter = string.Format("Date Range from {0} to {1}{2}", BeginDate.Value.ToShortDateString(), EndDate.Value.ToShortDateString(), Environment.NewLine);
			}

			return dateFilter + FilterValues;
		}
		#endregion
	}

	static class Extensions
	{
		public static void MapParameters<T, S>(this T target, S source) where T : class where S : class
		{
			MemberInfo member = default(MemberInfo);
			foreach (PropertyInfo property in source.GetType().GetProperties())
			{
				var attributes = property.GetCustomAttributes(typeof(ParameterValueAttribute), false);
				if (attributes[0] != null)
				{
					ParameterValueAttribute attribute = attributes[0] as ParameterValueAttribute;
					if (attribute.IsProperty)
					{
						foreach (PropertyInfo @this in target.GetType().GetProperties())
						{
							if (@this.Name.Equals(attribute.MapTo, StringComparison.OrdinalIgnoreCase))
								member = @this;
						}
					}
					else
					{
						foreach (FieldInfo @this in target.GetType().GetFields())
						{
							if (@this.Name.Equals(attribute.MapTo, StringComparison.OrdinalIgnoreCase))
								member = @this;
						}
					}

					switch (attribute.FilterType)
					{
						case FilterType.Boolean:
							if (member is PropertyInfo)
								(member as PropertyInfo).SetValue(target, property.GetValue(source, null).AsBoolean(), null);
							else
								(member as FieldInfo).SetValue(target, property.GetValue(source, null).AsBoolean());
							break;
						case FilterType.DateTime:
							if (member is PropertyInfo)
								(member as PropertyInfo).SetValue(target, property.GetValue(source, null).AsDateTime(), null);
							else
								(member as FieldInfo).SetValue(target, property.GetValue(source, null).AsDateTime());
							break;
						case FilterType.Decimal:
							if (member is PropertyInfo)
								(member as PropertyInfo).SetValue(target, property.GetValue(source, null).AsDecimal(), null);
							else
								(member as FieldInfo).SetValue(target, property.GetValue(source, null).AsDecimal());
							break;
						case FilterType.DomainEnum:
							if (member is PropertyInfo)
								(member as PropertyInfo).SetValue(target, property.GetValue(source, null).AsDomainEnum(), null);
							else
								(member as FieldInfo).SetValue(target, property.GetValue(source, null).AsDomainEnum());
							break;
						case FilterType.DomainObject:
							if (member is PropertyInfo)
								(member as PropertyInfo).SetValue(target, property.GetValue(source, null).AsDomainObject(), null);
							else
								(member as FieldInfo).SetValue(target, property.GetValue(source, null).AsDomainObject());
							break;
						case FilterType.Integer:
							if (member is PropertyInfo)
								(member as PropertyInfo).SetValue(target, property.GetValue(source, null).AsInteger(), null);
							else
								(member as FieldInfo).SetValue(target, property.GetValue(source, null).AsInteger());
							break;
						default:
							break;
					}
				}
			}
		}
	}

	class ParameterValueAttribute : Attribute
	{
		public FilterType FilterType { get; set; }
		public string MapTo { get; set; }
		public bool IsProperty { get; set; }
	}

	class ReportParameterCollection
	{
		[ParameterValue(FilterType = FilterType.DateTime, MapTo = "_beginDate")]
		public DateTime? BeginDate { get; set; }

		[ParameterValue(FilterType = FilterType.DateTime, MapTo = "_endDate")]
		public DateTime? EndDate { get; set; }

		[ParameterValue(FilterType = FilterType.DomainObject, MapTo = "_specificAgentIDs")]
		public string SpecificAgents { get; set; }

		[ParameterValue(FilterType = FilterType.DomainObject, MapTo = "_specificSubmittedByIds")]
		public string SpecificSubmittedBy { get; set; }

		[ParameterValue(FilterType = FilterType.DomainObject, MapTo = "_specificQuotingForIds")]
		public string SpecificQuotingFor { get; set; }

		[ParameterValue(FilterType = FilterType.DomainEnum, MapTo = "_specificGoverningRegionIds")]
		public string SpecificGoverningRegion { get; set; }
	}

	enum FilterType
	{
		Boolean,
		DateTime,
		Decimal,
		DomainEnum,
		DomainObject,
		Integer
	}
}

Open in new window

-saige-
0
pzozulkaAuthor Commented:
it_saige: Thanks. I'll give this a try. Since my issue has to do with essentially passing a string or sometimes an RDate by reference, I feel like I'm making a rookie mistake in my design, and what I'm trying to accomplish. But I'm not sure what. Why aren't my paramValues being assigned inside the method?
0
pzozulkaAuthor Commented:
It's really odd to me that I can pass a string (object) by reference, and have it be assigned a value inside the method, yet I can't to the same thing with a collection of strings (objects)?
0
it_saigeDeveloperCommented:
But you can...  Proof of concept:
using System;
using System.Collections.Generic;

namespace EE_Q28770879
{
	class Program
	{
		static void Main(string[] args)
		{
			List<string> stringList = new List<string>();
			string first = "First string";

			// We cannot manipulate strings without needing to pass them by reference.
			Console.WriteLine("Before the manipulate string method: {0}", first);
			ManipulateString(first);
			Console.WriteLine("After the manipulate string method: {0}", first);

			// We can pass a list and add items to the list.  Those items are still in the list without needing to pass by reference.
			// This is because a list is a reference type.
			AddString(stringList, "First string added");
			WriteList(stringList);
			for (int i = 0; i < 5; i++)
				AddString(stringList, string.Format("NewItem{0}", i));
			WriteList(stringList);
			Console.ReadLine();
		}

		private static void ManipulateString(string first)
		{
			first = string.Format("{0} added", first);
		}

		private static void AddString(List<string> target, string item)
		{
			if (target != null && !string.IsNullOrEmpty(item))
				target.Add(item);
		}

		private static void WriteList(List<string> source)
		{
			if (source != null)
			{
				Console.WriteLine("List contains {0} item{1}", source.Count, source.Count == 1 ? "." : "s.");
				foreach (var item in source)
					Console.WriteLine(item);
			}
		}
	}
}

Open in new window

Which produces the following output -Capture.JPG
From the code that you are presenting the ParamValue property associated to the "SpecificAgentsFilterName" will contain the string of specific agents so long as the parameter collection contains a string of specific agents to pass.  Realize, though, that the ParamValue is an object property and not the _specificAgentIds field.

This is why I stated that it looks like you are trying to implement a mapper.  The concept behind a mapper is that you can take a property/field in one object and map it to a different property/field in another object.

-saige-
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
.NET Programming

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.